diff --git a/cloud/scope/powervs_cluster.go b/cloud/scope/powervs_cluster.go index cf8311f93..bc206dd64 100644 --- a/cloud/scope/powervs_cluster.go +++ b/cloud/scope/powervs_cluster.go @@ -25,6 +25,7 @@ import ( "github.com/go-logr/logr" regionUtil "github.com/ppc64le-cloud/powervs-utils" + "k8s.io/klog/v2" "github.com/IBM-Cloud/power-go-client/ibmpisession" "github.com/IBM-Cloud/power-go-client/power/models" @@ -39,7 +40,6 @@ import ( "github.com/IBM/vpc-go-sdk/vpcv1" kerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/klog/v2" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -1081,16 +1081,16 @@ func (s *PowerVSClusterScope) createVPC() (*string, error) { // ReconcileVPCSubnets reconciles VPC subnet. func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) { subnets := make([]infrav1beta2.Subnet, 0) + vpcZones, err := regionUtil.VPCZonesForVPCRegion(*s.VPC().Region) + if err != nil { + return false, err + } + if len(vpcZones) == 0 { + return false, fmt.Errorf("error getting vpc zones error: %v", err) + } // check whether user has set the vpc subnets if len(s.IBMPowerVSCluster.Spec.VPCSubnets) == 0 { // if the user did not set any subnet, we try to create subnet in all the zones. - vpcZones, err := regionUtil.VPCZonesForVPCRegion(*s.VPC().Region) - if err != nil { - return false, err - } - if len(vpcZones) == 0 { - return false, fmt.Errorf("failed to fetch VPC zones, no zone found for region %s", *s.VPC().Region) - } for _, zone := range vpcZones { subnet := infrav1beta2.Subnet{ Name: ptr.To(fmt.Sprintf("%s-%s", *s.GetServiceName(infrav1beta2.ResourceTypeSubnet), zone)), @@ -1105,27 +1105,9 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) { } subnets = append(subnets, subnet) } - powerVSZone := s.Zone() - if powerVSZone == nil { - return false, fmt.Errorf("powervs zone is not set") - } - region := endpoints.ConstructRegionFromZone(*powerVSZone) - vpcZones, err := genUtil.VPCZonesForPowerVSRegion(region) - if err != nil { - return false, err - } - // TODO(karthik-k-n): Decide on using all zones or using one zone - if len(vpcZones) == 0 { - return false, fmt.Errorf("error getting vpc zones error: %v", err) - } - // TODO: Solution- use different CIDR from available zones - s.Info("New- Length of subnets and zones", "subnet", len(subnets), "zones", len(vpcZones)) - if len(subnets) < len(vpcZones) { - return false, fmt.Errorf("no enough vpcZones to use from PowerVS region please set zone with each subnet") - } - + subnetCount := make(map[string]int) for index, subnet := range subnets { - s.Info("Reconciling VPC subnets-new", "subnet", subnet) + s.Info("Reconciling VPC subnets", "subnet", subnet) var subnetID *string if subnet.ID != nil { subnetID = subnet.ID @@ -1163,17 +1145,26 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) { if subnet.Zone != nil { zone = *subnet.Zone } else { - zone = vpcZones[index] + if index < len(vpcZones) { + zone = vpcZones[index] + subnetCount[zone] = 0 + } else { + zone = vpcZones[index%len(vpcZones)] + subnetCount[zone]++ + } } s.V(3).Info("Creating VPC subnet") - subnetID, err = s.createVPCSubnet(subnet, zone) + subnetID, err = s.createVPCSubnet(subnet, zone, subnetCount[zone]) if err != nil { s.Error(err, "failed to create VPC subnet") return false, err } s.Info("Created VPC subnet", "id", subnetID) s.SetVPCSubnetID(*subnet.Name, infrav1beta2.ResourceReference{ID: subnetID, ControllerCreated: ptr.To(true)}) - return true, nil + // Requeue only when all subnets' creation are triggered + if index == len(vpcZones)-1 { + return true, nil + } } return false, nil } @@ -1192,39 +1183,35 @@ func (s *PowerVSClusterScope) checkVPCSubnet(subnetName string) (string, error) } // createVPCSubnet creates a VPC subnet. -func (s *PowerVSClusterScope) createVPCSubnet(subnet infrav1beta2.Subnet, zone string) (*string, error) { +func (s *PowerVSClusterScope) createVPCSubnet(subnet infrav1beta2.Subnet, zone string, subnetCount int) (*string, error) { // TODO(karthik-k-n): consider moving to clusterscope // fetch resource group id resourceGroupID := s.GetResourceGroupID() if resourceGroupID == "" { return nil, fmt.Errorf("failed to fetch resource group ID for resource group %v, ID is empty", s.ResourceGroup()) } - if subnet.Zone != nil { - zone = *subnet.Zone - } else { - vpcZones, err := regionUtil.VPCZonesForVPCRegion(*s.VPC().Region) - if err != nil { - return nil, err - } - // TODO(karthik-k-n): Decide on using all zones or using one zone - if len(vpcZones) == 0 { - return nil, fmt.Errorf("failed to fetch VPC zones, error: %v", err) - } - zone = vpcZones[0] - } // create subnet vpcID := s.GetVPCID() if vpcID == nil { return nil, fmt.Errorf("VPC ID is empty") } + addrPrefix, err := s.IBMVPCClient.GetSubnetAddrPrefix(*vpcID, zone) + if err != nil { + return nil, err + } + cidrBlock, err := genUtil.GetSubnetAddr(subnetCount, addrPrefix) + if err != nil { + return nil, err + } + s.V(3).Info("cidrBlock for subnet", "name", subnet.Name, "cidrBlock", cidrBlock) ipVersion := "ipv4" options := &vpcv1.CreateSubnetOptions{} options.SetSubnetPrototype(&vpcv1.SubnetPrototype{ - IPVersion: &ipVersion, - //Ipv4CIDRBlock: &cidrBlock, - Name: subnet.Name, + IPVersion: &ipVersion, + Ipv4CIDRBlock: &cidrBlock, + Name: subnet.Name, VPC: &vpcv1.VPCIdentity{ ID: vpcID, }, diff --git a/go.mod b/go.mod index 487d22c83..d5c429d51 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/IBM/networking-go-sdk v0.45.0 github.com/IBM/platform-services-go-sdk v0.65.0 github.com/IBM/vpc-go-sdk v0.55.0 + github.com/apparentlymart/go-cidr v1.1.0 github.com/blang/semver/v4 v4.0.0 github.com/coreos/ignition/v2 v2.19.0 github.com/go-logr/logr v1.4.1 diff --git a/util/util.go b/util/util.go index 552e3e797..bf2ab4669 100644 --- a/util/util.go +++ b/util/util.go @@ -18,9 +18,12 @@ package util import ( "fmt" + "math" + "net" regionUtil "github.com/ppc64le-cloud/powervs-utils" + "github.com/apparentlymart/go-cidr/cidr" "k8s.io/utils/ptr" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/endpoints" @@ -47,3 +50,21 @@ func GetTransitGatewayLocationAndRouting(powerVSZone *string, vpcRegion *string) // since VPC region is not set and used PowerVS region to calculate the transit gateway location, hence returning local routing as default. return &location, ptr.To(false), nil } + +func GetSubnetAddr(networkNum int, addrPrefix string) (string, error) { + _, ipv4Net, err := net.ParseCIDR(addrPrefix) + if err != nil { + return "", fmt.Errorf("error parsing CIDR address prefix: %w", err) + } + mask, _ := ipv4Net.Mask.Size() + // totalIPAddresses defines the prefix length of the subnet to be created + // TODO: Need to support different address count + totalIPAddresses := 256 + subnetPrefixBits := 32 - int(math.Ceil(math.Log2(float64(totalIPAddresses)))) //8 + subnet, err := cidr.Subnet(ipv4Net, subnetPrefixBits-mask, networkNum) + if err != nil { + return "", fmt.Errorf("error fetching subnet address: %w", err) + } + subnetAddr := fmt.Sprintf("%s/%d", subnet.IP, subnetPrefixBits) + return subnetAddr, nil +}