Skip to content

Commit

Permalink
CORS-2895/aws/capa: Discover Zones to set the cluster manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
mtulio committed Mar 13, 2024
1 parent 3f2e6f7 commit dfca517
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 49 deletions.
61 changes: 12 additions & 49 deletions pkg/asset/manifests/aws/cluster.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package aws

import (
"context"
"fmt"
"time"

Expand All @@ -13,32 +12,22 @@ import (

"github.com/openshift/installer/pkg/asset"
"github.com/openshift/installer/pkg/asset/installconfig"

"github.com/openshift/installer/pkg/asset/manifests/capiutils"
)

// GenerateClusterAssets generates the manifests for the cluster-api.
func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID *installconfig.ClusterID) (*capiutils.GenerateClusterAssetsOutput, error) {
func GenerateClusterAssets(ic *installconfig.InstallConfig, clusterID *installconfig.ClusterID) (*capiutils.GenerateClusterAssetsOutput, error) {
manifests := []*asset.RuntimeFile{}
mainCIDR := capiutils.CIDRFromInstallConfig(installConfig)

zones, err := installConfig.AWS.AvailabilityZones(context.TODO())
if err != nil {
return nil, errors.Wrap(err, "failed to get availability zones")
}

awsCluster := &capa.AWSCluster{
ObjectMeta: metav1.ObjectMeta{
Name: clusterID.InfraID,
Namespace: capiutils.Namespace,
},
Spec: capa.AWSClusterSpec{
Region: installConfig.Config.AWS.Region,
Region: ic.Config.AWS.Region,
NetworkSpec: capa.NetworkSpec{
VPC: capa.VPCSpec{
CidrBlock: mainCIDR.String(),
AvailabilityZoneUsageLimit: ptr.To(len(zones)),
AvailabilityZoneSelection: &capa.AZSelectionSchemeOrdered,
},
CNI: &capa.CNISpec{
CNIIngressRules: capa.CNIIngressRules{
{
Expand Down Expand Up @@ -164,41 +153,15 @@ func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID
},
}

// If the install config has subnets, use them.
if len(installConfig.AWS.Subnets) > 0 {
privateSubnets, err := installConfig.AWS.PrivateSubnets(context.TODO())
if err != nil {
return nil, errors.Wrap(err, "failed to get private subnets")
}
for _, subnet := range privateSubnets {
awsCluster.Spec.NetworkSpec.Subnets = append(awsCluster.Spec.NetworkSpec.Subnets, capa.SubnetSpec{
ID: subnet.ID,
CidrBlock: subnet.CIDR,
AvailabilityZone: subnet.Zone.Name,
IsPublic: subnet.Public,
})
}
publicSubnets, err := installConfig.AWS.PublicSubnets(context.TODO())
if err != nil {
return nil, errors.Wrap(err, "failed to get public subnets")
}

for _, subnet := range publicSubnets {
awsCluster.Spec.NetworkSpec.Subnets = append(awsCluster.Spec.NetworkSpec.Subnets, capa.SubnetSpec{
ID: subnet.ID,
CidrBlock: subnet.CIDR,
AvailabilityZone: subnet.Zone.Name,
IsPublic: subnet.Public,
})
}

vpc, err := installConfig.AWS.VPC(context.TODO())
if err != nil {
return nil, errors.Wrap(err, "failed to get VPC")
}
awsCluster.Spec.NetworkSpec.VPC = capa.VPCSpec{
ID: vpc,
}
// Set the VPC and zones (managed) or subnets (BYO VPC) based in the
// install-config.yaml.
err := setZones(&zoneConfigInput{
Config: ic,
ClusterID: clusterID,
Cluster: awsCluster,
})
if err != nil {
return nil, errors.Wrap(err, "failed to set cluster zones or subnets")
}

manifests = append(manifests, &asset.RuntimeFile{
Expand Down
182 changes: 182 additions & 0 deletions pkg/asset/manifests/aws/zones.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package aws

import (
"context"
"fmt"
"net"

"github.com/openshift/installer/pkg/asset/installconfig"
"github.com/openshift/installer/pkg/asset/installconfig/aws"
"github.com/openshift/installer/pkg/asset/manifests/capiutils"
utilscidr "github.com/openshift/installer/pkg/asset/manifests/capiutils/cidr"
"github.com/pkg/errors"
"k8s.io/utils/ptr"
capa "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"

"github.com/openshift/installer/pkg/types"
)

type zoneConfigInput struct {
Config *installconfig.InstallConfig
Cluster *capa.AWSCluster
ClusterID *installconfig.ClusterID
}

// setZones creates the CAPI NetworkSpec structures for managed or
// BYO VPC deployments from install-config.yaml.
func setZones(in *zoneConfigInput) error {
if len(in.Config.AWS.Subnets) > 0 {
return setZonesBYOVPC(in)
} else {
return setZonesManagedVPC(in)
}
}

// setZonesManagedVPC creates the CAPI NetworkSpec.Subnets setting the
// desired subnets from install-config.yaml in the BYO VPC deployment.
func setZonesBYOVPC(in *zoneConfigInput) error {
privateSubnets, err := in.Config.AWS.PrivateSubnets(context.TODO())
if err != nil {
return errors.Wrap(err, "failed to get private subnets")
}
for _, subnet := range privateSubnets {
in.Cluster.Spec.NetworkSpec.Subnets = append(in.Cluster.Spec.NetworkSpec.Subnets, capa.SubnetSpec{
ID: subnet.ID,
CidrBlock: subnet.CIDR,
AvailabilityZone: subnet.Zone.Name,
IsPublic: subnet.Public,
})
}

publicSubnets, err := in.Config.AWS.PublicSubnets(context.TODO())
if err != nil {
return errors.Wrap(err, "failed to get public subnets")
}
for _, subnet := range publicSubnets {
in.Cluster.Spec.NetworkSpec.Subnets = append(in.Cluster.Spec.NetworkSpec.Subnets, capa.SubnetSpec{
ID: subnet.ID,
CidrBlock: subnet.CIDR,
AvailabilityZone: subnet.Zone.Name,
IsPublic: subnet.Public,
})
}

vpc, err := in.Config.AWS.VPC(context.TODO())
if err != nil {
return errors.Wrap(err, "failed to get VPC")
}
in.Cluster.Spec.NetworkSpec.VPC = capa.VPCSpec{
ID: vpc,
}

return nil
}

// setZonesManagedVPC creates the CAPI NetworkSpec.VPC setting the
// desired zones from install-config.yaml in the managed VPC deployment.
func setZonesManagedVPC(in *zoneConfigInput) error {

zones, err := extractZonesFromInstallConfig(in)
if err != nil {
return errors.Wrap(err, "failed to get availability zones")
}

mainCIDR := capiutils.CIDRFromInstallConfig(in.Config)
in.Cluster.Spec.NetworkSpec.VPC = capa.VPCSpec{
CidrBlock: mainCIDR.String(),
AvailabilityZoneUsageLimit: ptr.To(len(zones)),
AvailabilityZoneSelection: &capa.AZSelectionSchemeOrdered,
}

// Base subnets considering only private zones, leaving one block free to allow
// future subnet expansions in Day-2.
numSubnets := len(zones) + 1

// Public subnets consumes one range from base blocks.
isPublishingExternal := in.Config.Config.Publish == types.ExternalPublishingStrategy
if isPublishingExternal {
numSubnets += 1
}

subnetsCIDRs, _ := utilscidr.SplitIntoSubnetsIPv4(mainCIDR.String(), numSubnets)
var publicSubnetsCIDRs []*net.IPNet
if isPublishingExternal {
publicSubnetsCIDRs, _ = utilscidr.SplitIntoSubnetsIPv4(subnetsCIDRs[len(zones)].String(), len(zones))
}

idxCIDR := 0
// Q: Can we use the standard terraform name (without 'subnet') and tell CAPA
// to query it for Control Planes?
subnetNamePrefix := fmt.Sprintf("%s-subnet", in.ClusterID.InfraID)
for _, zone := range zones {
if len(subnetsCIDRs) <= idxCIDR {
return errors.Wrap(err, "unable to define CIDR blocks for all private subnets")
}
cidr := subnetsCIDRs[idxCIDR]
in.Cluster.Spec.NetworkSpec.Subnets = append(in.Cluster.Spec.NetworkSpec.Subnets, capa.SubnetSpec{
ID: fmt.Sprintf("%s-private-%s", subnetNamePrefix, zone),
AvailabilityZone: zone.Name,
IsPublic: false,
CidrBlock: cidr.String(),
})
if isPublishingExternal {
if len(publicSubnetsCIDRs) <= idxCIDR {
return errors.Wrap(err, "unable to define CIDR blocks for all public subnets")
}
cidr = publicSubnetsCIDRs[idxCIDR]
in.Cluster.Spec.NetworkSpec.Subnets = append(in.Cluster.Spec.NetworkSpec.Subnets, capa.SubnetSpec{
ID: fmt.Sprintf("%s-public-%s", subnetNamePrefix, zone),
AvailabilityZone: zone.Name,
IsPublic: true,
CidrBlock: cidr.String(),
})
}
idxCIDR += 1
}

return nil
}

// extractZonesFromInstallConfig extract all zones defined in the install-config,
// otherwise discover it based in the AWS metadata when none is defined.
// TODO: Open Question: What is the expected behavior when only one pool defines the
// zones? Should the cluster be limited to those zones? Eg when worker defines single
// zone, and no controlPlane.platform.aws.zones is defined.
func extractZonesFromInstallConfig(in *zoneConfigInput) ([]*aws.Zone, error) {

var zones []*aws.Zone
zonesMap := make(map[string]struct{})

cfg := in.Config.Config
if len(cfg.ControlPlane.Platform.AWS.Zones) > 0 {
for _, zone := range cfg.ControlPlane.Platform.AWS.Zones {
if _, ok := zonesMap[zone]; !ok {
zones = append(zones, &aws.Zone{Name: zone})
}
}
}
for _, compute := range cfg.Compute {
if compute.Platform.AWS == nil {
continue
}
if len(compute.Platform.AWS.Zones) > 0 {
for _, zone := range compute.Platform.AWS.Zones {
if _, ok := zonesMap[zone]; !ok {
zones = append(zones, &aws.Zone{Name: zone})
}
}
}
}

if len(zones) == 0 {
zonesMeta, err := in.Config.AWS.AvailabilityZones(context.TODO())
if err != nil {
return nil, errors.Wrap(err, "failed to get availability zones")
}
for _, zoneMeta := range zonesMeta {
zones = append(zones, &aws.Zone{Name: zoneMeta})
}
}

return zones, nil
}

0 comments on commit dfca517

Please sign in to comment.