github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/ibmcloud/validation_test.go (about)

     1  package ibmcloud
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/IBM/go-sdk-core/v5/core"
     9  	"github.com/IBM/networking-go-sdk/dnsrecordsv1"
    10  	"github.com/IBM/platform-services-go-sdk/resourcemanagerv2"
    11  	"github.com/IBM/vpc-go-sdk/vpcv1"
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/stretchr/testify/assert"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/utils/ptr"
    16  
    17  	configv1 "github.com/openshift/api/config/v1"
    18  	"github.com/openshift/installer/pkg/asset/installconfig/ibmcloud/mock"
    19  	"github.com/openshift/installer/pkg/asset/installconfig/ibmcloud/responses"
    20  	"github.com/openshift/installer/pkg/ipnet"
    21  	"github.com/openshift/installer/pkg/types"
    22  	ibmcloudtypes "github.com/openshift/installer/pkg/types/ibmcloud"
    23  )
    24  
    25  type editFunctions []func(ic *types.InstallConfig)
    26  
    27  var (
    28  	validRegion                  = "us-south"
    29  	validCIDR                    = "10.0.0.0/16"
    30  	validCISInstanceCRN          = "crn:v1:bluemix:public:internet-svcs:global:a/valid-account-id:valid-instance-id::"
    31  	validClusterName             = "valid-cluster-name"
    32  	validDNSZoneID               = "valid-zone-id"
    33  	validBaseDomain              = "valid.base.domain"
    34  	validPublicSubnetUSSouth1ID  = "public-subnet-us-south-1-id"
    35  	validPublicSubnetUSSouth2ID  = "public-subnet-us-south-2-id"
    36  	validPrivateSubnetUSSouth1ID = "private-subnet-us-south-1-id"
    37  	validPrivateSubnetUSSouth2ID = "private-subnet-us-south-2-id"
    38  	validSubnets                 = []string{
    39  		validPublicSubnetUSSouth1ID,
    40  		validPublicSubnetUSSouth2ID,
    41  		validPrivateSubnetUSSouth1ID,
    42  		validPrivateSubnetUSSouth2ID,
    43  	}
    44  	validSubnet1Name  = "valid-subnet-1"
    45  	validSubnet2Name  = "valid-subnet-2"
    46  	validSubnet3Name  = "valid-subnet-3"
    47  	validVPCID        = "valid-id"
    48  	validVPC          = "valid-vpc"
    49  	validRG           = "valid-resource-group"
    50  	validZoneUSSouth1 = "us-south-1"
    51  	validZoneUSSouth2 = "us-south-2"
    52  	validZoneUSSouth3 = "us-south-3"
    53  	validZones        = []string{
    54  		validZoneUSSouth1,
    55  		validZoneUSSouth2,
    56  		validZoneUSSouth3,
    57  	}
    58  	validZoneSubnetNameMap = map[string]string{
    59  		validZoneUSSouth1: validSubnet1Name,
    60  		validZoneUSSouth2: validSubnet2Name,
    61  		validZoneUSSouth3: validSubnet3Name,
    62  	}
    63  
    64  	wrongRG           = "wrong-resource-group"
    65  	wrongSubnetName   = "wrong-subnet"
    66  	wrongVPCID        = "wrong-id"
    67  	wrongVPC          = "wrong-vpc"
    68  	wrongZone         = "wrong-zone"
    69  	anotherValidVPCID = "another-valid-id"
    70  	anotherValidVPC   = "another-valid-vpc"
    71  	anotherValidRG    = "another-valid-resource-group"
    72  
    73  	validResourceGroups = []resourcemanagerv2.ResourceGroup{
    74  		{
    75  			Name: &validRG,
    76  			ID:   &validRG,
    77  		},
    78  		{
    79  			Name: &anotherValidRG,
    80  			ID:   &anotherValidRG,
    81  		},
    82  	}
    83  	validVPCs = []vpcv1.VPC{
    84  		{
    85  			Name: &validVPC,
    86  			ID:   &validVPCID,
    87  			ResourceGroup: &vpcv1.ResourceGroupReference{
    88  				Name: &validRG,
    89  				ID:   &validRG,
    90  			},
    91  		},
    92  		{
    93  			Name: &anotherValidVPC,
    94  			ID:   &anotherValidVPCID,
    95  			ResourceGroup: &vpcv1.ResourceGroupReference{
    96  				Name: &anotherValidRG,
    97  				ID:   &anotherValidRG,
    98  			},
    99  		},
   100  	}
   101  	invalidVPC = []vpcv1.VPC{
   102  		{
   103  			Name: &wrongVPC,
   104  			ID:   &wrongVPCID,
   105  			ResourceGroup: &vpcv1.ResourceGroupReference{
   106  				Name: &validRG,
   107  				ID:   &validRG,
   108  			},
   109  		},
   110  	}
   111  	validVPCInvalidRG = []vpcv1.VPC{
   112  		{
   113  			Name: &validVPC,
   114  			ID:   &validVPCID,
   115  			ResourceGroup: &vpcv1.ResourceGroupReference{
   116  				Name: &wrongRG,
   117  				ID:   &wrongRG,
   118  			},
   119  		},
   120  	}
   121  	validSubnet1 = &vpcv1.Subnet{
   122  		Name: &validSubnet1Name,
   123  		VPC: &vpcv1.VPCReference{
   124  			Name: &validVPC,
   125  			ID:   &validVPCID,
   126  		},
   127  		ResourceGroup: &vpcv1.ResourceGroupReference{
   128  			Name: &validRG,
   129  			ID:   &validRG,
   130  		},
   131  		Zone: &vpcv1.ZoneReference{
   132  			Name: &validZoneUSSouth1,
   133  		},
   134  	}
   135  	validSubnet2 = &vpcv1.Subnet{
   136  		Name: &validSubnet2Name,
   137  		VPC: &vpcv1.VPCReference{
   138  			Name: &validVPC,
   139  			ID:   &validVPCID,
   140  		},
   141  		ResourceGroup: &vpcv1.ResourceGroupReference{
   142  			Name: &validRG,
   143  			ID:   &validRG,
   144  		},
   145  		Zone: &vpcv1.ZoneReference{
   146  			Name: &validZoneUSSouth2,
   147  		},
   148  	}
   149  	validSubnet3 = &vpcv1.Subnet{
   150  		Name: &validSubnet3Name,
   151  		VPC: &vpcv1.VPCReference{
   152  			Name: &validVPC,
   153  			ID:   &validVPCID,
   154  		},
   155  		ResourceGroup: &vpcv1.ResourceGroupReference{
   156  			Name: &validRG,
   157  			ID:   &validRG,
   158  		},
   159  		Zone: &vpcv1.ZoneReference{
   160  			Name: &validZoneUSSouth3,
   161  		},
   162  	}
   163  	wrongSubnet = &vpcv1.Subnet{
   164  		Name: &wrongSubnetName,
   165  		VPC: &vpcv1.VPCReference{
   166  			Name: &validVPC,
   167  			ID:   &validVPCID,
   168  		},
   169  		ResourceGroup: &vpcv1.ResourceGroupReference{
   170  			Name: &validRG,
   171  			ID:   &validRG,
   172  		},
   173  		Zone: &vpcv1.ZoneReference{
   174  			Name: &wrongZone,
   175  		},
   176  	}
   177  
   178  	validInstanceProfiles = []vpcv1.InstanceProfile{{Name: &[]string{"type-a"}[0]}, {Name: &[]string{"type-b"}[0]}}
   179  
   180  	invalidEncryptionKey = "invalid-encryption-key-crn"
   181  	validEncryptionKey   = "valid-encryption-key-crn"
   182  
   183  	validEncryptionKeyResponse = &responses.EncryptionKeyResponse{
   184  		CRN:     validEncryptionKey,
   185  		State:   1,
   186  		Deleted: ptr.To(false),
   187  	}
   188  
   189  	disabledEncryptionKeyResponse = &responses.EncryptionKeyResponse{
   190  		CRN:     validEncryptionKey,
   191  		State:   0,
   192  		Deleted: ptr.To(false),
   193  	}
   194  
   195  	deletedEncryptionKeyResponse = &responses.EncryptionKeyResponse{
   196  		CRN:     validEncryptionKey,
   197  		State:   1,
   198  		Deleted: ptr.To(true),
   199  	}
   200  
   201  	existingDNSRecordsResponse = []dnsrecordsv1.DnsrecordDetails{
   202  		{
   203  			ID: core.StringPtr("valid-dns-record-1"),
   204  		},
   205  		{
   206  			ID: core.StringPtr("valid-dns-record-2"),
   207  		},
   208  	}
   209  	noDNSRecordsResponse = []dnsrecordsv1.DnsrecordDetails{}
   210  
   211  	unittestURL = "e2e.unittest.local"
   212  )
   213  
   214  func validInstallConfig() *types.InstallConfig {
   215  	return &types.InstallConfig{
   216  		ObjectMeta: metav1.ObjectMeta{
   217  			Name: validClusterName,
   218  		},
   219  		BaseDomain: validBaseDomain,
   220  		Networking: &types.Networking{
   221  			MachineNetwork: []types.MachineNetworkEntry{
   222  				{CIDR: *ipnet.MustParseCIDR(validCIDR)},
   223  			},
   224  		},
   225  		Publish: types.ExternalPublishingStrategy,
   226  		Platform: types.Platform{
   227  			IBMCloud: validMinimalPlatform(),
   228  		},
   229  		ControlPlane: &types.MachinePool{
   230  			Platform: types.MachinePoolPlatform{
   231  				IBMCloud: validMachinePool(),
   232  			},
   233  		},
   234  		Compute: []types.MachinePool{{
   235  			Platform: types.MachinePoolPlatform{
   236  				IBMCloud: validMachinePool(),
   237  			},
   238  		}},
   239  	}
   240  }
   241  
   242  func validMinimalPlatform() *ibmcloudtypes.Platform {
   243  	return &ibmcloudtypes.Platform{
   244  		Region: validRegion,
   245  	}
   246  }
   247  
   248  func validMachinePool() *ibmcloudtypes.MachinePool {
   249  	return &ibmcloudtypes.MachinePool{}
   250  }
   251  
   252  func validResourceGroupName(ic *types.InstallConfig) {
   253  	ic.Platform.IBMCloud.ResourceGroupName = "valid-resource-group"
   254  }
   255  
   256  func validNetworkResourceGroupName(ic *types.InstallConfig) {
   257  	ic.Platform.IBMCloud.NetworkResourceGroupName = "valid-resource-group"
   258  }
   259  
   260  func validVPCName(ic *types.InstallConfig) {
   261  	ic.Platform.IBMCloud.VPCName = "valid-vpc"
   262  }
   263  
   264  func validControlPlaneSubnetsForZones(ic *types.InstallConfig, zones []string) {
   265  	// If no zones are passed, we select all valid zones
   266  	if zones == nil || len(zones) == 0 {
   267  		zones = validZones
   268  	}
   269  	for _, zone := range zones {
   270  		ic.Platform.IBMCloud.ControlPlaneSubnets = append(ic.Platform.IBMCloud.ControlPlaneSubnets, validZoneSubnetNameMap[zone])
   271  	}
   272  }
   273  
   274  func validComputeSubnetsForZones(ic *types.InstallConfig, zones []string) {
   275  	// If no zones are passed, we select all valid zones
   276  	if zones == nil || len(zones) == 0 {
   277  		zones = validZones
   278  	}
   279  	for _, zone := range zones {
   280  		ic.Platform.IBMCloud.ComputeSubnets = append(ic.Platform.IBMCloud.ComputeSubnets, validZoneSubnetNameMap[zone])
   281  	}
   282  }
   283  
   284  func controlPlaneInvalidBootVolume(ic *types.InstallConfig) {
   285  	ic.ControlPlane.Platform.IBMCloud = &ibmcloudtypes.MachinePool{
   286  		BootVolume: &ibmcloudtypes.BootVolume{
   287  			EncryptionKey: invalidEncryptionKey,
   288  		},
   289  	}
   290  }
   291  
   292  func controlPlaneValidBootVolume(ic *types.InstallConfig) {
   293  	ic.ControlPlane.Platform.IBMCloud = &ibmcloudtypes.MachinePool{
   294  		BootVolume: &ibmcloudtypes.BootVolume{
   295  			EncryptionKey: validEncryptionKey,
   296  		},
   297  	}
   298  }
   299  
   300  func controlPlaneInvalidInstanceType(ic *types.InstallConfig) {
   301  	ic.ControlPlane.Platform.IBMCloud = &ibmcloudtypes.MachinePool{
   302  		InstanceType: "invalid-instance-type",
   303  	}
   304  }
   305  
   306  func controlPlaneValidInstanceType(ic *types.InstallConfig) {
   307  	ic.ControlPlane.Platform.IBMCloud = &ibmcloudtypes.MachinePool{
   308  		InstanceType: "type-a",
   309  	}
   310  }
   311  
   312  func TestValidate(t *testing.T) {
   313  	cases := []struct {
   314  		name     string
   315  		edits    editFunctions
   316  		errorMsg string
   317  	}{
   318  		{
   319  			name:     "valid install config",
   320  			edits:    editFunctions{},
   321  			errorMsg: "",
   322  		},
   323  		{
   324  			name: "VPC with no network ResourceGroup supplied",
   325  			edits: editFunctions{
   326  				validVPCName,
   327  			},
   328  			errorMsg: `platform.ibmcloud.networkResourceGroupName: Invalid value: "": networkResourceGroupName cannot be empty when providing a vpcName: valid-vpc`,
   329  		},
   330  		{
   331  			name: "VPC not found",
   332  			edits: editFunctions{
   333  				validNetworkResourceGroupName,
   334  				func(ic *types.InstallConfig) {
   335  					ic.Platform.IBMCloud.VPCName = "missing-vpc"
   336  				},
   337  			},
   338  			errorMsg: `vpcName: Not found: "missing-vpc"$`,
   339  		},
   340  		{
   341  			name: "VPC not in network ResourceGroup",
   342  			edits: editFunctions{
   343  				func(ic *types.InstallConfig) {
   344  					ic.Platform.IBMCloud.NetworkResourceGroupName = wrongRG
   345  				},
   346  				validVPCName,
   347  			},
   348  			errorMsg: `platform.ibmcloud.vpcName: Invalid value: "valid-vpc": vpc is not in provided Network ResourceGroup: wrong-resource-group`,
   349  		},
   350  		{
   351  			name: "VPC with no control plane subnets",
   352  			edits: editFunctions{
   353  				validNetworkResourceGroupName,
   354  				validVPCName,
   355  			},
   356  			errorMsg: `\Qplatform.ibmcloud.controlPlaneSubnets: Invalid value: []string(nil): controlPlaneSubnets cannot be empty when providing a vpcName: valid-vpc\E`,
   357  		},
   358  		{
   359  			name: "control plane subnet not found",
   360  			edits: editFunctions{
   361  				validNetworkResourceGroupName,
   362  				validVPCName,
   363  				func(ic *types.InstallConfig) {
   364  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{"missing-cp-subnet"}
   365  				},
   366  			},
   367  			errorMsg: `platform.ibmcloud.controlPlaneSubnets: Not found: "missing-cp-subnet"`,
   368  		},
   369  		{
   370  			name: "control plane subnet IBM error",
   371  			edits: editFunctions{
   372  				validNetworkResourceGroupName,
   373  				validVPCName,
   374  				func(ic *types.InstallConfig) {
   375  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{"ibm-error-cp-subnet"}
   376  				},
   377  			},
   378  			errorMsg: `platform.ibmcloud.controlPlaneSubnets: Internal error: ibmcloud error`,
   379  		},
   380  		{
   381  			name: "control plane subnet invalid VPC",
   382  			edits: editFunctions{
   383  				validNetworkResourceGroupName,
   384  				func(ic *types.InstallConfig) {
   385  					ic.Platform.IBMCloud.VPCName = "wrong-vpc"
   386  				},
   387  				func(ic *types.InstallConfig) {
   388  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{"valid-subnet"}
   389  				},
   390  			},
   391  			errorMsg: `platform.ibmcloud.controlPlaneSubnets: Invalid value: "valid-subnet": controlPlaneSubnets contains subnet: valid-subnet, not found in expected vpcID: wrong-id`,
   392  		},
   393  		{
   394  			name: "control plane subnet invalid ResourceGroup",
   395  			edits: editFunctions{
   396  				func(ic *types.InstallConfig) {
   397  					ic.Platform.IBMCloud.NetworkResourceGroupName = wrongRG
   398  				},
   399  				validVPCName,
   400  				func(ic *types.InstallConfig) {
   401  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{"valid-subnet"}
   402  				},
   403  			},
   404  			errorMsg: `platform.ibmcloud.controlPlaneSubnets: Invalid value: "valid-subnet": controlPlaneSubnets contains subnet: valid-subnet, not found in expected networkResourceGroupName: wrong-resource-group`,
   405  		},
   406  		{
   407  			name: "control plane subnet no zones",
   408  			edits: editFunctions{
   409  				validNetworkResourceGroupName,
   410  				validVPCName,
   411  				func(ic *types.InstallConfig) {
   412  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   413  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   414  				},
   415  			},
   416  		},
   417  		{
   418  			name: "control plane subnet no machinepoolplatform",
   419  			edits: editFunctions{
   420  				validNetworkResourceGroupName,
   421  				validVPCName,
   422  				func(ic *types.InstallConfig) {
   423  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   424  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   425  				},
   426  				func(ic *types.InstallConfig) {
   427  					ic.ControlPlane.Platform.IBMCloud = nil
   428  				},
   429  			},
   430  		},
   431  		{
   432  			name: "control plane subnet invalid zones",
   433  			edits: editFunctions{
   434  				validNetworkResourceGroupName,
   435  				validVPCName,
   436  				func(ic *types.InstallConfig) {
   437  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name}
   438  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   439  				},
   440  				func(ic *types.InstallConfig) {
   441  					ic.ControlPlane.Platform.IBMCloud.Zones = validZones
   442  				},
   443  			},
   444  			errorMsg: `\Qplatform.ibmcloud.controlPlaneSubnets: Invalid value: []string{"valid-subnet-1"}: number of zones (1) covered by controlPlaneSubnets does not match number of provided or default zones (3) for control plane in us-south\E`,
   445  		},
   446  		{
   447  			name: "control plane subnet valid zones some",
   448  			edits: editFunctions{
   449  				validNetworkResourceGroupName,
   450  				validVPCName,
   451  				func(ic *types.InstallConfig) {
   452  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet2Name, validSubnet3Name}
   453  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   454  				},
   455  				func(ic *types.InstallConfig) {
   456  					ic.ControlPlane.Platform.IBMCloud.Zones = []string{"us-south-2", "us-south-3"}
   457  				},
   458  			},
   459  		},
   460  		{
   461  			name: "control plane subnet valid zones all",
   462  			edits: editFunctions{
   463  				validNetworkResourceGroupName,
   464  				validVPCName,
   465  				func(ic *types.InstallConfig) {
   466  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   467  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   468  				},
   469  				func(ic *types.InstallConfig) {
   470  					ic.ControlPlane.Platform.IBMCloud.Zones = validZones
   471  				},
   472  			},
   473  		},
   474  		{
   475  			name: "VPC with no compute subnets",
   476  			edits: editFunctions{
   477  				validNetworkResourceGroupName,
   478  				validVPCName,
   479  			},
   480  			errorMsg: `\Qplatform.ibmcloud.computeSubnets: Invalid value: []string(nil): computeSubnets cannot be empty when providing a vpcName: valid-vpc\E`,
   481  		},
   482  		{
   483  			name: "compute subnet not found",
   484  			edits: editFunctions{
   485  				validNetworkResourceGroupName,
   486  				validVPCName,
   487  				func(ic *types.InstallConfig) {
   488  					ic.Platform.IBMCloud.ComputeSubnets = []string{"missing-compute-subnet"}
   489  				},
   490  			},
   491  			errorMsg: `platform.ibmcloud.computeSubnets: Not found: "missing-compute-subnet"`,
   492  		},
   493  		{
   494  			name: "compute subnet IBM error",
   495  			edits: editFunctions{
   496  				validNetworkResourceGroupName,
   497  				validVPCName,
   498  				func(ic *types.InstallConfig) {
   499  					ic.Platform.IBMCloud.ComputeSubnets = []string{"ibm-error-compute-subnet"}
   500  				},
   501  			},
   502  			errorMsg: `platform.ibmcloud.computeSubnets: Internal error: ibmcloud error`,
   503  		},
   504  		{
   505  			name: "compute subnet invalid VPC",
   506  			edits: editFunctions{
   507  				validNetworkResourceGroupName,
   508  				func(ic *types.InstallConfig) {
   509  					ic.Platform.IBMCloud.VPCName = "wrong-vpc"
   510  				},
   511  				func(ic *types.InstallConfig) {
   512  					ic.Platform.IBMCloud.ComputeSubnets = []string{"valid-subnet"}
   513  				},
   514  			},
   515  			errorMsg: `platform.ibmcloud.computeSubnets: Invalid value: "valid-subnet": computeSubnets contains subnet: valid-subnet, not found in expected vpcID: wrong-id`,
   516  		},
   517  		{
   518  			name: "compute subnet invalid ResourceGroup",
   519  			edits: editFunctions{
   520  				func(ic *types.InstallConfig) {
   521  					ic.Platform.IBMCloud.NetworkResourceGroupName = wrongRG
   522  				},
   523  				validVPCName,
   524  				func(ic *types.InstallConfig) {
   525  					ic.Platform.IBMCloud.ComputeSubnets = []string{"valid-subnet"}
   526  				},
   527  			},
   528  			errorMsg: `platform.ibmcloud.computeSubnets: Invalid value: "valid-subnet": computeSubnets contains subnet: valid-subnet, not found in expected networkResourceGroupName: wrong-resource-group`,
   529  		},
   530  		{
   531  			name: "compute subnet no zones",
   532  			edits: editFunctions{
   533  				validNetworkResourceGroupName,
   534  				validVPCName,
   535  				func(ic *types.InstallConfig) {
   536  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   537  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   538  				},
   539  			},
   540  		},
   541  		{
   542  			name: "compute subnet no machinepoolplatform",
   543  			edits: editFunctions{
   544  				validNetworkResourceGroupName,
   545  				validVPCName,
   546  				func(ic *types.InstallConfig) {
   547  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   548  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   549  				},
   550  				func(ic *types.InstallConfig) {
   551  					ic.Compute[0].Platform.IBMCloud = nil
   552  				},
   553  			},
   554  		},
   555  		{
   556  			name: "compute subnet invalid zones",
   557  			edits: editFunctions{
   558  				validNetworkResourceGroupName,
   559  				validVPCName,
   560  				func(ic *types.InstallConfig) {
   561  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   562  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name}
   563  				},
   564  				func(ic *types.InstallConfig) {
   565  					ic.Compute[0].Platform.IBMCloud.Zones = validZones
   566  				},
   567  			},
   568  			errorMsg: `\Qplatform.ibmcloud.computeSubnets: Invalid value: []string{"valid-subnet-1"}: number of zones (1) covered by computeSubnets does not match number of provided or default zones (3) for compute[0] in us-south\E`,
   569  		},
   570  		{
   571  			name: "single compute subnet valid zones some",
   572  			edits: editFunctions{
   573  				validNetworkResourceGroupName,
   574  				validVPCName,
   575  				func(ic *types.InstallConfig) {
   576  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   577  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet2Name}
   578  				},
   579  				func(ic *types.InstallConfig) {
   580  					ic.Compute[0].Platform.IBMCloud.Zones = []string{validZoneUSSouth2}
   581  				},
   582  			},
   583  		},
   584  		{
   585  			name: "multiple compute subnet invalid zones some",
   586  			edits: editFunctions{
   587  				validNetworkResourceGroupName,
   588  				validVPCName,
   589  				func(ic *types.InstallConfig) {
   590  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   591  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet2Name, validSubnet3Name}
   592  				},
   593  				func(ic *types.InstallConfig) {
   594  					secondCompute := types.MachinePool{
   595  						Platform: types.MachinePoolPlatform{
   596  							IBMCloud: validMachinePool(),
   597  						},
   598  					}
   599  					ic.Compute = append(ic.Compute, secondCompute)
   600  					ic.Compute[0].Platform.IBMCloud.Zones = []string{validZoneUSSouth2, validZoneUSSouth3}
   601  					ic.Compute[1].Platform.IBMCloud.Zones = []string{validZoneUSSouth3}
   602  				},
   603  			},
   604  			errorMsg: `\Qplatform.ibmcloud.computeSubnets: Invalid value: []string{"valid-subnet-2", "valid-subnet-3"}: number of zones (2) covered by computeSubnets does not match number of provided or default zones (1) for compute[1] in us-south\E`,
   605  		},
   606  		{
   607  			name: "multiple compute subnet valid zones some",
   608  			edits: editFunctions{
   609  				validNetworkResourceGroupName,
   610  				validVPCName,
   611  				func(ic *types.InstallConfig) {
   612  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   613  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet2Name, validSubnet3Name}
   614  				},
   615  				func(ic *types.InstallConfig) {
   616  					secondCompute := types.MachinePool{
   617  						Platform: types.MachinePoolPlatform{
   618  							IBMCloud: validMachinePool(),
   619  						},
   620  					}
   621  					ic.Compute = append(ic.Compute, secondCompute)
   622  					ic.Compute[0].Platform.IBMCloud.Zones = []string{validZoneUSSouth2, validZoneUSSouth3}
   623  					ic.Compute[1].Platform.IBMCloud.Zones = []string{validZoneUSSouth2, validZoneUSSouth3}
   624  				},
   625  			},
   626  		},
   627  		{
   628  			name: "single compute subnet valid zones all",
   629  			edits: editFunctions{
   630  				validNetworkResourceGroupName,
   631  				validVPCName,
   632  				func(ic *types.InstallConfig) {
   633  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   634  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   635  				},
   636  				func(ic *types.InstallConfig) {
   637  					ic.Compute[0].Platform.IBMCloud.Zones = validZones
   638  				},
   639  			},
   640  		},
   641  		{
   642  			name: "multiple compute subnet valid zones all",
   643  			edits: editFunctions{
   644  				validNetworkResourceGroupName,
   645  				validVPCName,
   646  				func(ic *types.InstallConfig) {
   647  					ic.Platform.IBMCloud.ControlPlaneSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   648  					ic.Platform.IBMCloud.ComputeSubnets = []string{validSubnet1Name, validSubnet2Name, validSubnet3Name}
   649  				},
   650  				func(ic *types.InstallConfig) {
   651  					secondCompute := types.MachinePool{
   652  						Platform: types.MachinePoolPlatform{
   653  							IBMCloud: validMachinePool(),
   654  						},
   655  					}
   656  					ic.Compute = append(ic.Compute, secondCompute)
   657  					ic.Compute[0].Platform.IBMCloud.Zones = validZones
   658  					ic.Compute[1].Platform.IBMCloud.Zones = validZones
   659  				},
   660  			},
   661  		},
   662  		{
   663  			name: "invalid control plane machine pool type",
   664  			edits: editFunctions{
   665  				controlPlaneInvalidInstanceType,
   666  			},
   667  			errorMsg: `\QcontrolPlane.platform.ibmcloud.type: Not found: "invalid-instance-type"\E`,
   668  		},
   669  		{
   670  			name: "valid control plane machine pool type",
   671  			edits: editFunctions{
   672  				controlPlaneValidInstanceType,
   673  			},
   674  		},
   675  		{
   676  			name: "invalid control plane machine pool boot volume crn",
   677  			edits: editFunctions{
   678  				controlPlaneInvalidBootVolume,
   679  			},
   680  			errorMsg: `\QcontrolPlane.platform.ibmcloud.bootVolume.encryptionKey: Invalid value: "invalid-encryption-key-crn": key CRN does not match: valid-encryption-key-crn\E`,
   681  		},
   682  		{
   683  			name: "invalid control plane machine pool boot volume disabled key",
   684  			edits: editFunctions{
   685  				controlPlaneValidBootVolume,
   686  			},
   687  			errorMsg: `\QcontrolPlane.platform.ibmcloud.bootVolume.encryptionKey: Invalid value: "valid-encryption-key-crn": key is disabled\E`,
   688  		},
   689  		{
   690  			name: "invalid control plane machine pool boot volume deleted key",
   691  			edits: editFunctions{
   692  				controlPlaneValidBootVolume,
   693  			},
   694  			errorMsg: `\QcontrolPlane.platform.ibmcloud.bootVolume.encryptionKey: Invalid value: "valid-encryption-key-crn": key has been deleted\E`,
   695  		},
   696  		{
   697  			name: "valid control plane machine pool boot volume crn",
   698  			edits: editFunctions{
   699  				controlPlaneValidBootVolume,
   700  			},
   701  		},
   702  	}
   703  
   704  	mockCtrl := gomock.NewController(t)
   705  	defer mockCtrl.Finish()
   706  
   707  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   708  
   709  	// Mocks: valid install config and all other tests ('AnyTimes()')
   710  	ibmcloudClient.EXPECT().GetSubnet(gomock.Any(), validPublicSubnetUSSouth1ID).Return(&vpcv1.Subnet{Zone: &vpcv1.ZoneReference{Name: &validZoneUSSouth1}}, nil).AnyTimes()
   711  	ibmcloudClient.EXPECT().GetSubnet(gomock.Any(), validPublicSubnetUSSouth2ID).Return(&vpcv1.Subnet{Zone: &vpcv1.ZoneReference{Name: &validZoneUSSouth1}}, nil).AnyTimes()
   712  	ibmcloudClient.EXPECT().GetSubnet(gomock.Any(), validPrivateSubnetUSSouth1ID).Return(&vpcv1.Subnet{Zone: &vpcv1.ZoneReference{Name: &validZoneUSSouth1}}, nil).AnyTimes()
   713  	ibmcloudClient.EXPECT().GetSubnet(gomock.Any(), validPrivateSubnetUSSouth2ID).Return(&vpcv1.Subnet{Zone: &vpcv1.ZoneReference{Name: &validZoneUSSouth1}}, nil).AnyTimes()
   714  	ibmcloudClient.EXPECT().GetSubnet(gomock.Any(), "subnet-invalid-zone").Return(&vpcv1.Subnet{Zone: &vpcv1.ZoneReference{Name: &[]string{"invalid"}[0]}}, nil).AnyTimes()
   715  	ibmcloudClient.EXPECT().GetVSIProfiles(gomock.Any()).Return(validInstanceProfiles, nil).AnyTimes()
   716  	ibmcloudClient.EXPECT().GetVPCZonesForRegion(gomock.Any(), validRegion).Return([]string{"us-south-1", "us-south-2", "us-south-3"}, nil).AnyTimes()
   717  
   718  	// Mocks: VPC with no ResourceGroup supplied
   719  	// No mocks required
   720  
   721  	// Mocks: VPC not found
   722  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   723  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   724  
   725  	// Mocks: VPC not in ResourceGroup
   726  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   727  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   728  
   729  	// Mocks: VPC with no control plane subnets
   730  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   731  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   732  
   733  	// Mocks: control plane subnet not found
   734  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   735  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   736  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "missing-cp-subnet", validRegion).Return(nil, &VPCResourceNotFoundError{})
   737  
   738  	// Mocks: control plane subnet IBM error
   739  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   740  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   741  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "ibm-error-cp-subnet", validRegion).Return(nil, errors.New("ibmcloud error"))
   742  
   743  	// Mocks: control plane subnet invalid VPC
   744  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   745  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(invalidVPC, nil)
   746  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "valid-subnet", validRegion).Return(validSubnet1, nil)
   747  
   748  	// Mocks: control plane subnet invalid ResourceGroup
   749  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   750  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCInvalidRG, nil)
   751  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "valid-subnet", validRegion).Return(validSubnet1, nil)
   752  
   753  	// Mocks: control plane subnet no zones
   754  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   755  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   756  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   757  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   758  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   759  
   760  	// Mocks: control plane subnet no machinepoolplatform
   761  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   762  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   763  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   764  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   765  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   766  
   767  	// Mocks: control plane subnet invalid zones
   768  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   769  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   770  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   771  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil)
   772  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil)
   773  
   774  	// Mocks: control plane subnet valid zones some
   775  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   776  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   777  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil)
   778  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   779  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   780  
   781  	// Mocks: control plane subnet valid zones all
   782  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   783  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   784  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   785  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   786  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   787  
   788  	// Mocks: VPC with no compute subnets
   789  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   790  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   791  
   792  	// Mocks: compute subnet not found
   793  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   794  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   795  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "missing-compute-subnet", validRegion).Return(nil, &VPCResourceNotFoundError{})
   796  
   797  	// Mocks: compute subnet IBM error
   798  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   799  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   800  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "ibm-error-compute-subnet", validRegion).Return(nil, errors.New("ibmcloud error"))
   801  
   802  	// Mocks: compute subnet invalid VPC
   803  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   804  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(invalidVPC, nil)
   805  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "valid-subnet", validRegion).Return(validSubnet1, nil)
   806  
   807  	// Mocks: compute subnet invalid ResourceGroup
   808  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   809  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCInvalidRG, nil)
   810  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), "valid-subnet", validRegion).Return(validSubnet1, nil)
   811  
   812  	// Mocks: compute subnet no zones
   813  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   814  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   815  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   816  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   817  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   818  
   819  	// Mocks: compute subnet no machinepoolplatform
   820  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   821  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   822  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   823  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   824  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   825  
   826  	// Mocks: compute subnet invalid zones
   827  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   828  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   829  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   830  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil)
   831  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil)
   832  
   833  	// Mocks: single compute subnet valid zones some
   834  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   835  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   836  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil)
   837  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   838  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil)
   839  
   840  	// Mocks: multiple compute subnet invalid zones some
   841  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   842  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   843  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil)
   844  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   845  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   846  
   847  	// Mocks: multiple compute subnet valid zones some
   848  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   849  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   850  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil)
   851  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   852  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   853  
   854  	// Mocks: single compute subnet valid zones all
   855  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   856  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   857  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   858  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   859  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   860  
   861  	// Mocks: multiple compute subnet valid zones all
   862  	ibmcloudClient.EXPECT().GetResourceGroups(gomock.Any()).Return(validResourceGroups, nil)
   863  	ibmcloudClient.EXPECT().GetVPCs(gomock.Any(), validRegion).Return(validVPCs, nil)
   864  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet1Name, validRegion).Return(validSubnet1, nil).Times(2)
   865  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet2Name, validRegion).Return(validSubnet2, nil).Times(2)
   866  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), validSubnet3Name, validRegion).Return(validSubnet3, nil).Times(2)
   867  
   868  	// Mocks: invalid control plane machine pool type
   869  	// No additional mocks required
   870  
   871  	// Mocks: valid control plane machine pool type
   872  	// No additional mocks required
   873  
   874  	// Mocks: invalid control plane machine pool boot volume crn
   875  	ibmcloudClient.EXPECT().GetEncryptionKey(gomock.Any(), invalidEncryptionKey).Return(validEncryptionKeyResponse, nil)
   876  
   877  	// Mocks: invalid control plane machine pool boot volume disabled
   878  	ibmcloudClient.EXPECT().GetEncryptionKey(gomock.Any(), validEncryptionKey).Return(disabledEncryptionKeyResponse, nil)
   879  
   880  	// Mocks: invalid control plane machine pool boot volume deleted
   881  	ibmcloudClient.EXPECT().GetEncryptionKey(gomock.Any(), validEncryptionKey).Return(deletedEncryptionKeyResponse, nil)
   882  
   883  	// Mock: valid control plane machine pool boot volume crn
   884  	ibmcloudClient.EXPECT().GetEncryptionKey(gomock.Any(), validEncryptionKey).Return(validEncryptionKeyResponse, nil)
   885  
   886  	for _, tc := range cases {
   887  		t.Run(tc.name, func(t *testing.T) {
   888  			editedInstallConfig := validInstallConfig()
   889  			for _, edit := range tc.edits {
   890  				edit(editedInstallConfig)
   891  			}
   892  
   893  			aggregatedErrors := Validate(ibmcloudClient, editedInstallConfig)
   894  			if tc.errorMsg != "" {
   895  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   896  			} else {
   897  				assert.NoError(t, aggregatedErrors)
   898  			}
   899  		})
   900  	}
   901  }
   902  
   903  func TestValidatePreExistingPublicDNS(t *testing.T) {
   904  	cases := []struct {
   905  		name     string
   906  		internal bool
   907  		edits    editFunctions
   908  		errorMsg string
   909  	}{
   910  		{
   911  			name:     "no pre-existing External DNS records",
   912  			internal: false,
   913  			errorMsg: "",
   914  		},
   915  		{
   916  			name:     "pre-existing External DNS records",
   917  			internal: false,
   918  			errorMsg: `^record api\.valid-cluster-name\.valid\.base\.domain already exists in CIS zone \(valid-zone-id\) and might be in use by another cluster, please remove it to continue$`,
   919  		},
   920  		{
   921  			name:     "cannot get External zone ID",
   922  			internal: false,
   923  			errorMsg: `^baseDomain: Internal error$`,
   924  		},
   925  		{
   926  			name:     "cannot get External DNS records",
   927  			internal: false,
   928  			errorMsg: `^baseDomain: Internal error$`,
   929  		},
   930  		{
   931  			name:     "no validation of Internal PublishStrategy",
   932  			internal: true,
   933  			errorMsg: "",
   934  		},
   935  	}
   936  
   937  	mockCtrl := gomock.NewController(t)
   938  	defer mockCtrl.Finish()
   939  
   940  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   941  
   942  	dnsRecordName := fmt.Sprintf("api.%s.%s", validClusterName, validBaseDomain)
   943  
   944  	metadata := NewMetadata(&types.InstallConfig{
   945  		BaseDomain: validBaseDomain,
   946  		Platform: types.Platform{
   947  			IBMCloud: &ibmcloudtypes.Platform{
   948  				Region: "us-south",
   949  			},
   950  		},
   951  	})
   952  	metadata.cisInstanceCRN = validCISInstanceCRN
   953  
   954  	// Mocks: no pre-existing External DNS records
   955  	ibmcloudClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return(validDNSZoneID, nil)
   956  	ibmcloudClient.EXPECT().GetDNSRecordsByName(gomock.Any(), validCISInstanceCRN, validDNSZoneID, dnsRecordName).Return(noDNSRecordsResponse, nil)
   957  
   958  	// Mocks: pre-existing External DNS records
   959  	ibmcloudClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return(validDNSZoneID, nil)
   960  	ibmcloudClient.EXPECT().GetDNSRecordsByName(gomock.Any(), validCISInstanceCRN, validDNSZoneID, dnsRecordName).Return(existingDNSRecordsResponse, nil)
   961  
   962  	// Mocks: cannot get External zone ID
   963  	ibmcloudClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return("", fmt.Errorf(""))
   964  
   965  	// Mocks: cannot get External DNS records
   966  	ibmcloudClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return(validDNSZoneID, nil)
   967  	ibmcloudClient.EXPECT().GetDNSRecordsByName(gomock.Any(), validCISInstanceCRN, validDNSZoneID, dnsRecordName).Return(nil, fmt.Errorf(""))
   968  
   969  	for _, tc := range cases {
   970  		t.Run(tc.name, func(t *testing.T) {
   971  			validInstallConfig := validInstallConfig()
   972  			if tc.internal {
   973  				validInstallConfig.Publish = types.InternalPublishingStrategy
   974  			}
   975  			aggregatedErrors := ValidatePreExistingPublicDNS(ibmcloudClient, validInstallConfig, metadata)
   976  			if tc.errorMsg != "" {
   977  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   978  			} else {
   979  				assert.NoError(t, aggregatedErrors)
   980  			}
   981  		})
   982  	}
   983  }
   984  
   985  func TestValidateServiceEndpoints(t *testing.T) {
   986  	cases := []struct {
   987  		name     string
   988  		edits    editFunctions
   989  		errorMsg string
   990  	}{
   991  		{
   992  			name: "empty service endpoints",
   993  		},
   994  		{
   995  			name: "single valid service endpoint",
   996  			edits: editFunctions{
   997  				func(ic *types.InstallConfig) {
   998  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
   999  						{
  1000  							Name: configv1.IBMCloudServiceIAM,
  1001  							URL:  unittestURL,
  1002  						},
  1003  					}
  1004  				},
  1005  			},
  1006  		},
  1007  		{
  1008  			name: "single invalid override service endpoint",
  1009  			edits: editFunctions{
  1010  				func(ic *types.InstallConfig) {
  1011  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
  1012  						{
  1013  							Name: "invalid-service",
  1014  							URL:  unittestURL,
  1015  						},
  1016  					}
  1017  				},
  1018  			},
  1019  			errorMsg: `\Qplatform.ibmcloud.serviceEndpoints[0].name: Invalid value: "invalid-service": not a supported override service\E`,
  1020  		},
  1021  		{
  1022  			name: "single invalid URI service endpoint",
  1023  			edits: editFunctions{
  1024  				func(ic *types.InstallConfig) {
  1025  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
  1026  						{
  1027  							Name: configv1.IBMCloudServiceIAM,
  1028  							URL:  "/invalid/uri/format",
  1029  						},
  1030  					}
  1031  				},
  1032  			},
  1033  			errorMsg: `\Qplatform.ibmcloud.serviceEndpoints[0].url: Invalid value: "/invalid/uri/format": Head "/invalid/uri/format": unsupported protocol scheme ""\E`,
  1034  		},
  1035  		{
  1036  			name: "multiple valid service endpoint",
  1037  			edits: editFunctions{
  1038  				func(ic *types.InstallConfig) {
  1039  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
  1040  						{
  1041  							Name: configv1.IBMCloudServiceIAM,
  1042  							URL:  unittestURL,
  1043  						},
  1044  						{
  1045  							Name: configv1.IBMCloudServiceCOS,
  1046  							URL:  unittestURL,
  1047  						},
  1048  					}
  1049  				},
  1050  			},
  1051  		},
  1052  		{
  1053  			name: "multiple invalid duplicate service endpoints",
  1054  			edits: editFunctions{
  1055  				func(ic *types.InstallConfig) {
  1056  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
  1057  						{
  1058  							Name: configv1.IBMCloudServiceIAM,
  1059  							URL:  unittestURL,
  1060  						},
  1061  						{
  1062  							Name: configv1.IBMCloudServiceIAM,
  1063  							URL:  unittestURL,
  1064  						},
  1065  					}
  1066  				},
  1067  			},
  1068  			errorMsg: `\Qplatform.ibmcloud.serviceEndpoints[1].name: Duplicate value: "IAM"\E`,
  1069  		},
  1070  		{
  1071  			name: "multiple invalid service endpoints",
  1072  			edits: editFunctions{
  1073  				func(ic *types.InstallConfig) {
  1074  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
  1075  						{
  1076  							Name: "invalid-service",
  1077  							URL:  unittestURL,
  1078  						},
  1079  						{
  1080  							Name: configv1.IBMCloudServiceCOS,
  1081  							URL:  "/invalid/uri/format",
  1082  						},
  1083  						{
  1084  							Name: configv1.IBMCloudServiceCOS,
  1085  							URL:  unittestURL,
  1086  						},
  1087  					}
  1088  				},
  1089  			},
  1090  			errorMsg: `\Q[platform.ibmcloud.serviceEndpoints[0].name: Invalid value: "invalid-service": not a supported override service, platform.ibmcloud.serviceEndpoints[1].url: Invalid value: "/invalid/uri/format": Head "/invalid/uri/format": unsupported protocol scheme "", platform.ibmcloud.serviceEndpoints[2].name: Duplicate value: "COS"]\E`,
  1091  		},
  1092  		{
  1093  			name: "multiple mixed validity service endpoints",
  1094  			edits: editFunctions{
  1095  				func(ic *types.InstallConfig) {
  1096  					ic.Platform.IBMCloud.ServiceEndpoints = []configv1.IBMCloudServiceEndpoint{
  1097  						{
  1098  							Name: configv1.IBMCloudServiceIAM,
  1099  							URL:  unittestURL,
  1100  						},
  1101  						{
  1102  							Name: configv1.IBMCloudServiceCOS,
  1103  							URL:  unittestURL,
  1104  						},
  1105  						{
  1106  							Name: configv1.IBMCloudServiceCOS,
  1107  							URL:  "/invalid/uri/format",
  1108  						},
  1109  						{
  1110  							Name: "invalid-service",
  1111  							URL:  unittestURL,
  1112  						},
  1113  					}
  1114  				},
  1115  			},
  1116  			errorMsg: `\Qplatform.ibmcloud.serviceEndpoints[2].name: Duplicate value: "COS", platform.ibmcloud.serviceEndpoints[3].name: Invalid value: "invalid-service": not a supported override service\E`,
  1117  		},
  1118  	}
  1119  
  1120  	for _, tc := range cases {
  1121  		t.Run(tc.name, func(t *testing.T) {
  1122  			editedInstallConfig := validInstallConfig()
  1123  			for _, edit := range tc.edits {
  1124  				edit(editedInstallConfig)
  1125  			}
  1126  			aggregatedErrors := ValidateServiceEndpoints(editedInstallConfig)
  1127  			if tc.errorMsg != "" {
  1128  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
  1129  			} else {
  1130  				assert.NoError(t, aggregatedErrors)
  1131  			}
  1132  		})
  1133  	}
  1134  }