Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]: Use multiple zones in case of multiple subnets #1793

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 38 additions & 29 deletions cloud/scope/powervs_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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)),
Expand All @@ -1105,8 +1105,9 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) {
}
subnets = append(subnets, subnet)
}
for _, subnet := range subnets {
s.Info("Reconciling VPC subnet", "subnet", subnet)
subnetCount := make(map[string]int)
for index, subnet := range subnets {
s.Info("Reconciling VPC subnets", "subnet", subnet)
var subnetID *string
if subnet.ID != nil {
subnetID = subnet.ID
Expand Down Expand Up @@ -1140,16 +1141,33 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets() (bool, error) {
// check for next subnet
continue
}

var zone string
if subnet.Zone != nil {
zone = *subnet.Zone
} else {
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)
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(subnets)-1 {
return true, nil
}
}
return false, nil
}
Expand All @@ -1168,37 +1186,28 @@ func (s *PowerVSClusterScope) checkVPCSubnet(subnetName string) (string, error)
}

// createVPCSubnet creates a VPC subnet.
func (s *PowerVSClusterScope) createVPCSubnet(subnet infrav1beta2.Subnet) (*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())
}
var zone string
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")
}
cidrBlock, err := s.IBMVPCClient.GetSubnetAddrPrefix(*vpcID, zone)
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{}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}