diff --git a/cloud/scope/powervs_cluster.go b/cloud/scope/powervs_cluster.go index 827104725..ad7bd3da3 100644 --- a/cloud/scope/powervs_cluster.go +++ b/cloud/scope/powervs_cluster.go @@ -24,7 +24,7 @@ import ( "strings" "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 +39,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" @@ -1106,13 +1105,6 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) { // 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)), @@ -1123,7 +1115,7 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) { } else { subnets = append(subnets, s.IBMPowerVSCluster.Spec.VPCSubnets...) } - + subnetCount := make(map[string]int) for index, subnet := range subnets { s.Info("Reconciling VPC subnet", "subnet", subnet) var subnetID *string @@ -1168,17 +1160,29 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) { if subnet.Zone != nil { zone = *subnet.Zone } else { - zone = vpcZones[index] + if index < len(vpcZones) { + zone = vpcZones[index] + } else { + zone = vpcZones[index%len(vpcZones)] + } + } + if _, ok := subnetCount[zone]; ok { + subnetCount[zone]++ + } else { + subnetCount[zone] = 0 } 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", "subnetID", subnetID) + s.Info("Created VPC subnet", "id", subnetID) s.SetVPCSubnetStatus(*subnet.Name, infrav1beta2.ResourceReference{ID: subnetID, ControllerCreated: ptr.To(true)}) - return true, nil + // Requeue only when all subnets' creation are triggered + if index == len(subnets)-1 { + return true, nil + } } return false, nil } @@ -1197,39 +1201,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 87b474eb8..bb2054c76 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.69.3 github.com/IBM/vpc-go-sdk v0.61.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.2 diff --git a/util/util.go b/util/util.go index 552e3e797..c42088d3b 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: totalIPAddresses should be provided by user instead of hard coding + totalIPAddresses := 256 + subnetPrefixBits := 32 - int(math.Ceil(math.Log2(float64(totalIPAddresses)))) + 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 +}