Skip to content

Commit

Permalink
IBMCloud: BYON Enablement - InstallConfig
Browse files Browse the repository at this point in the history
Setup enablement for Bring Your Own Network support on IBM Cloud
using IPI. This portion focuses on adding support for the
required values in the InstallConfig, and performing simple
validation of those resources provided.
  • Loading branch information
cjschaef committed Jul 13, 2022
1 parent 2a61b87 commit 60aeec4
Show file tree
Hide file tree
Showing 13 changed files with 585 additions and 19 deletions.
28 changes: 19 additions & 9 deletions data/data/install.openshift.io_installconfigs.yaml
Expand Up @@ -2220,6 +2220,18 @@ spec:
description: IBMCloud is the configuration used when installing on
IBM Cloud.
properties:
computeSubnets:
description: ComputeSubnets are the names of already existing
subnets where the cluster compute nodes should be created.
items:
type: string
type: array
controlPlaneSubnets:
description: ControlPlaneSubnets are the names of already existing
subnets where the cluster control plane nodes should be created.
items:
type: string
type: array
defaultMachinePlatform:
description: DefaultMachinePlatform is the default configuration
used when installing on IBM Cloud for machine pools which do
Expand Down Expand Up @@ -2270,15 +2282,13 @@ spec:
will be created.
type: string
resourceGroupName:
description: "ResourceGroupName is the name of an already existing
resource group where the cluster should be installed. This resource
group should only be used for this specific cluster and the
cluster components will assume ownership of all resources in
the resource group. Destroying the cluster using installer will
delete this resource group. \n This resource group must be empty
with no other resources when trying to use it for creating a
cluster. If empty, a new resource group will be created for
the cluster."
description: ResourceGroupName is the name of an already existing
resource group where the cluster should be installed. If empty,
a new resource group will be created for the cluster.
type: string
vpcName:
description: VPCName is the name of an already existing VPC where
the cluster should be installed.
type: string
required:
- region
Expand Down
1 change: 1 addition & 0 deletions pkg/asset/cluster/ibmcloud/ibmcloud.go
Expand Up @@ -20,5 +20,6 @@ func Metadata(infraID string, config *types.InstallConfig, meta *icibmcloud.Meta
CISInstanceCRN: cisCrn,
Region: config.Platform.IBMCloud.Region,
ResourceGroupName: config.Platform.IBMCloud.ClusterResourceGroupName(infraID),
VPC: config.Platform.IBMCloud.GetVPCName(),
}
}
4 changes: 4 additions & 0 deletions pkg/asset/cluster/tfvars.go
Expand Up @@ -471,6 +471,9 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
workerConfigs[i] = w.Spec.Template.Spec.ProviderSpec.Value.Object.(*ibmcloudprovider.IBMCloudMachineProviderSpec)
}

// Set existing network (boolean of whether one is being used)
preexistingVPC := installConfig.Config.Platform.IBMCloud.GetVPCName() != ""

// Set machine pool info
var masterMachinePool ibmcloud.MachinePool
var workerMachinePool ibmcloud.MachinePool
Expand Down Expand Up @@ -534,6 +537,7 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
ImageURL: string(*rhcosImage),
MasterConfigs: masterConfigs,
MasterDedicatedHosts: masterDedicatedHosts,
PreexistingVPC: preexistingVPC,
PublishStrategy: installConfig.Config.Publish,
ResourceGroupName: installConfig.Config.Platform.IBMCloud.ResourceGroupName,
WorkerConfigs: workerConfigs,
Expand Down
42 changes: 42 additions & 0 deletions pkg/asset/installconfig/ibmcloud/client.go
Expand Up @@ -32,8 +32,10 @@ type API interface {
GetResourceGroups(ctx context.Context) ([]resourcemanagerv2.ResourceGroup, error)
GetResourceGroup(ctx context.Context, nameOrID string) (*resourcemanagerv2.ResourceGroup, error)
GetSubnet(ctx context.Context, subnetID string) (*vpcv1.Subnet, error)
GetSubnetByName(ctx context.Context, subnetName string, region string) (*vpcv1.Subnet, error)
GetVSIProfiles(ctx context.Context) ([]vpcv1.InstanceProfile, error)
GetVPC(ctx context.Context, vpcID string) (*vpcv1.VPC, error)
GetVPCs(ctx context.Context, region string) ([]vpcv1.VPC, error)
GetVPCZonesForRegion(ctx context.Context, region string) ([]string, error)
SetVPCServiceURLForRegion(ctx context.Context, region string) error
}
Expand Down Expand Up @@ -340,6 +342,25 @@ func (c *Client) GetSubnet(ctx context.Context, subnetID string) (*vpcv1.Subnet,
return subnet, err
}

// GetSubnetByName gets a subnet by its Name.
func (c *Client) GetSubnetByName(ctx context.Context, subnetName string, region string) (*vpcv1.Subnet, error) {
_, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()

c.SetVPCServiceURLForRegion(ctx, region)
listSubnetsOptions := c.vpcAPI.NewListSubnetsOptions()
subnetCollection, detailedResponse, err := c.vpcAPI.ListSubnetsWithContext(ctx, listSubnetsOptions)
if detailedResponse.GetStatusCode() == http.StatusNotFound {
return nil, err
}
for _, subnet := range subnetCollection.Subnets {
if subnetName == *subnet.Name {
return &subnet, nil
}
}
return nil, &VPCResourceNotFoundError{}
}

// GetVSIProfiles gets a list of all VSI profiles.
func (c *Client) GetVSIProfiles(ctx context.Context) ([]vpcv1.InstanceProfile, error) {
listInstanceProfilesOptions := c.vpcAPI.NewListInstanceProfilesOptions()
Expand Down Expand Up @@ -378,6 +399,27 @@ func (c *Client) GetVPC(ctx context.Context, vpcID string) (*vpcv1.VPC, error) {
return nil, &VPCResourceNotFoundError{}
}

// GetVPCs gets all VPCs in a region
func (c *Client) GetVPCs(ctx context.Context, region string) ([]vpcv1.VPC, error) {
_, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()

err := c.SetVPCServiceURLForRegion(ctx, region)
if err != nil {
return nil, errors.Wrap(err, "failed to set vpc api service url")
}

allVPCs := []vpcv1.VPC{}
if vpcs, detailedResponse, err := c.vpcAPI.ListVpcs(c.vpcAPI.NewListVpcsOptions()); err != nil {
if detailedResponse.GetStatusCode() != http.StatusNotFound {
return nil, err
}
} else if vpcs != nil {
allVPCs = append(allVPCs, vpcs.Vpcs...)
}
return allVPCs, nil
}

// GetVPCZonesForRegion gets the supported zones for a VPC region.
func (c *Client) GetVPCZonesForRegion(ctx context.Context, region string) ([]string, error) {
_, cancel := context.WithTimeout(ctx, 1*time.Minute)
Expand Down
30 changes: 30 additions & 0 deletions pkg/asset/installconfig/ibmcloud/mock/ibmcloudclient_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 88 additions & 0 deletions pkg/asset/installconfig/ibmcloud/validation.go
Expand Up @@ -42,6 +42,10 @@ func validatePlatform(client API, ic *types.InstallConfig, path *field.Path) fie
allErrs = append(allErrs, validateResourceGroup(client, ic, path)...)
}

if ic.Platform.IBMCloud.VPCName != "" {
allErrs = append(allErrs, validateExistingVPC(client, ic, path)...)
}

if ic.Platform.IBMCloud.DefaultMachinePlatform != nil {
allErrs = append(allErrs, validateMachinePool(client, ic.IBMCloud, ic.Platform.IBMCloud.DefaultMachinePlatform, path)...)
}
Expand Down Expand Up @@ -218,6 +222,90 @@ func validateResourceGroup(client API, ic *types.InstallConfig, path *field.Path
return allErrs
}

func validateExistingVPC(client API, ic *types.InstallConfig, path *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

if ic.IBMCloud.VPCName == "" {
return allErrs
}

if ic.IBMCloud.ResourceGroupName == "" {
return append(allErrs, field.NotFound(path.Child("resourceGroupName"), ic.IBMCloud.ResourceGroupName))
}

vpcs, err := client.GetVPCs(context.TODO(), ic.IBMCloud.Region)
if err != nil {
return append(allErrs, field.InternalError(path.Child("vpcName"), err))
}

found := false
for _, vpc := range vpcs {
if *vpc.Name == ic.IBMCloud.VPCName {
if *vpc.ResourceGroup.ID != ic.IBMCloud.ResourceGroupName && *vpc.ResourceGroup.Name != ic.IBMCloud.ResourceGroupName {
return append(allErrs, field.Invalid(path.Child("vpcName"), ic.IBMCloud.VPCName, fmt.Sprintf("vpc is not in provided ResourceGroup: %s", ic.IBMCloud.ResourceGroupName)))
}
found = true
allErrs = append(allErrs, validateExistingSubnets(client, ic, path, *vpc.ID)...)
break
}
}

if !found {
allErrs = append(allErrs, field.NotFound(path.Child("vpcName"), ic.IBMCloud.VPCName))
}
return allErrs
}

func validateExistingSubnets(client API, ic *types.InstallConfig, path *field.Path, vpcID string) field.ErrorList {
allErrs := field.ErrorList{}

if len(ic.IBMCloud.ControlPlaneSubnets) == 0 {
allErrs = append(allErrs, field.Invalid(path.Child("controlPlaneSubnets"), ic.IBMCloud.ControlPlaneSubnets, fmt.Sprintf("controlPlaneSubnets cannot be empty when providing a vpcName: %s", ic.IBMCloud.VPCName)))
} else {
for _, controlPlaneSubnet := range ic.IBMCloud.ControlPlaneSubnets {
subnet, err := client.GetSubnetByName(context.TODO(), controlPlaneSubnet, ic.IBMCloud.Region)
if err != nil {
if errors.Is(err, &VPCResourceNotFoundError{}) {
allErrs = append(allErrs, field.NotFound(path.Child("controlPlaneSubnets"), controlPlaneSubnet))
} else {
allErrs = append(allErrs, field.InternalError(path.Child("controlPlaneSubnets"), err))
}
} else {
if *subnet.VPC.ID != vpcID {
allErrs = append(allErrs, field.Invalid(path.Child("controlPlaneSubnets"), controlPlaneSubnet, fmt.Sprintf("controlPlaneSubnets contains subnet: %s, not found in expected vpcID: %s", controlPlaneSubnet, vpcID)))
}
if *subnet.ResourceGroup.ID != ic.IBMCloud.ResourceGroupName && *subnet.ResourceGroup.Name != ic.IBMCloud.ResourceGroupName {
allErrs = append(allErrs, field.Invalid(path.Child("controlPlaneSubnets"), controlPlaneSubnet, fmt.Sprintf("controlPlaneSubnets contains subnet: %s, not found in expected resourceGroupName: %s", controlPlaneSubnet, ic.IBMCloud.ResourceGroupName)))
}
}
}
}

if len(ic.IBMCloud.ComputeSubnets) == 0 {
allErrs = append(allErrs, field.Invalid(path.Child("computeSubnets"), ic.IBMCloud.ComputeSubnets, fmt.Sprintf("computeSubnets cannot be empty when providing a vpcName: %s", ic.IBMCloud.VPCName)))
} else {
for _, computeSubnet := range ic.IBMCloud.ComputeSubnets {
subnet, err := client.GetSubnetByName(context.TODO(), computeSubnet, ic.IBMCloud.Region)
if err != nil {
if errors.Is(err, &VPCResourceNotFoundError{}) {
allErrs = append(allErrs, field.NotFound(path.Child("computeSubnets"), computeSubnet))
} else {
allErrs = append(allErrs, field.InternalError(path.Child("computeSubnets"), err))
}
} else {
if *subnet.VPC.ID != vpcID {
allErrs = append(allErrs, field.Invalid(path.Child("computeSubnets"), computeSubnet, fmt.Sprintf("computeSubnets contains subnet: %s, not found in expected vpcID: %s", computeSubnet, vpcID)))
}
if *subnet.ResourceGroup.ID != ic.IBMCloud.ResourceGroupName && *subnet.ResourceGroup.Name != ic.IBMCloud.ResourceGroupName {
allErrs = append(allErrs, field.Invalid(path.Child("computeSubnets"), computeSubnet, fmt.Sprintf("computeSubnets contains subnet: %s, not found in expected resourceGroupName: %s", computeSubnet, ic.IBMCloud.ResourceGroupName)))
}
}
}
}

return allErrs
}

func validateSubnetZone(client API, subnetID string, validZones sets.String, subnetPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if subnet, err := client.GetSubnet(context.TODO(), subnetID); err == nil {
Expand Down

0 comments on commit 60aeec4

Please sign in to comment.