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

     1  package powervs_test
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/IBM/vpc-go-sdk/vpcv1"
     9  	"github.com/golang/mock/gomock"
    10  	"github.com/stretchr/testify/assert"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  
    14  	machinev1 "github.com/openshift/api/machine/v1"
    15  	machinev1beta1 "github.com/openshift/api/machine/v1beta1"
    16  	"github.com/openshift/installer/pkg/asset/installconfig/powervs"
    17  	"github.com/openshift/installer/pkg/asset/installconfig/powervs/mock"
    18  	"github.com/openshift/installer/pkg/ipnet"
    19  	"github.com/openshift/installer/pkg/types"
    20  	powervstypes "github.com/openshift/installer/pkg/types/powervs"
    21  )
    22  
    23  type editFunctions []func(ic *types.InstallConfig)
    24  
    25  var (
    26  	validRegion                  = "dal"
    27  	validCIDR                    = "192.168.0.0/24"
    28  	validCISInstanceCRN          = "crn:v1:bluemix:public:internet-svcs:global:a/valid-account-id:valid-instance-id::"
    29  	validClusterName             = "valid-cluster-name"
    30  	validDNSZoneID               = "valid-zone-id"
    31  	validBaseDomain              = "valid.base.domain"
    32  	validPowerVSResourceGroup    = "valid-resource-group"
    33  	validPublicSubnetUSSouth1ID  = "public-subnet-us-south-1-id"
    34  	validPublicSubnetUSSouth2ID  = "public-subnet-us-south-2-id"
    35  	validPrivateSubnetUSSouth1ID = "private-subnet-us-south-1-id"
    36  	validPrivateSubnetUSSouth2ID = "private-subnet-us-south-2-id"
    37  	validSubnets                 = []string{
    38  		validPublicSubnetUSSouth1ID,
    39  		validPublicSubnetUSSouth2ID,
    40  		validPrivateSubnetUSSouth1ID,
    41  		validPrivateSubnetUSSouth2ID,
    42  	}
    43  	validUserID = "valid-user@example.com"
    44  	validZone   = "dal10"
    45  
    46  	existingDNSRecordsResponse = []powervs.DNSRecordResponse{
    47  		{
    48  			Name: "valid-dns-record-name-1",
    49  			Type: "valid-dns-record-type",
    50  		},
    51  		{
    52  			Name: "valid-dns-record-name-2",
    53  			Type: "valid-dns-record-type",
    54  		},
    55  	}
    56  	noDNSRecordsResponse   = []powervs.DNSRecordResponse{}
    57  	invalidArchitecture    = func(ic *types.InstallConfig) { ic.ControlPlane.Architecture = "ppc64" }
    58  	cidrInvalid, _         = ipnet.ParseCIDR("192.168.0.0/16")
    59  	invalidMachinePoolCIDR = func(ic *types.InstallConfig) { ic.Networking.MachineNetwork[0].CIDR = *cidrInvalid }
    60  	cidrValid, _           = ipnet.ParseCIDR("192.168.0.0/24")
    61  	validMachinePoolCIDR   = func(ic *types.InstallConfig) { ic.Networking.MachineNetwork[0].CIDR = *cidrValid }
    62  	validVPCRegion         = "us-south"
    63  	invalidVPCRegion       = "foo-bah"
    64  	setValidVPCRegion      = func(ic *types.InstallConfig) { ic.Platform.PowerVS.VPCRegion = validVPCRegion }
    65  	validRG                = "valid-resource-group"
    66  	anotherValidRG         = "another-valid-resource-group"
    67  	validVPCID             = "valid-id"
    68  	anotherValidVPCID      = "another-valid-id"
    69  	validVPC               = "valid-vpc"
    70  	setValidVPCName        = func(ic *types.InstallConfig) { ic.Platform.PowerVS.VPCName = validVPC }
    71  	anotherValidVPC        = "another-valid-vpc"
    72  	invalidVPC             = "bogus-vpc"
    73  	validVPCs              = []vpcv1.VPC{
    74  		{
    75  			Name: &validVPC,
    76  			ID:   &validVPCID,
    77  			ResourceGroup: &vpcv1.ResourceGroupReference{
    78  				Name: &validRG,
    79  				ID:   &validRG,
    80  			},
    81  		},
    82  		{
    83  			Name: &anotherValidVPC,
    84  			ID:   &anotherValidVPCID,
    85  			ResourceGroup: &vpcv1.ResourceGroupReference{
    86  				Name: &anotherValidRG,
    87  				ID:   &anotherValidRG,
    88  			},
    89  		},
    90  	}
    91  	validVPCSubnet   = "valid-vpc-subnet"
    92  	invalidVPCSubnet = "invalid-vpc-subnet"
    93  	wrongVPCSubnet   = "wrong-vpc-subnet"
    94  	validSubnet      = &vpcv1.Subnet{
    95  		Name: &validRG,
    96  		VPC: &vpcv1.VPCReference{
    97  			Name: &validVPC,
    98  			ID:   &validVPCID,
    99  		},
   100  		ResourceGroup: &vpcv1.ResourceGroupReference{
   101  			Name: &validRG,
   102  			ID:   &validRG,
   103  		},
   104  	}
   105  	wrongSubnet = &vpcv1.Subnet{
   106  		Name: &validRG,
   107  		VPC: &vpcv1.VPCReference{
   108  			Name: &anotherValidVPC,
   109  			ID:   &anotherValidVPCID,
   110  		},
   111  		ResourceGroup: &vpcv1.ResourceGroupReference{
   112  			Name: &validRG,
   113  			ID:   &validRG,
   114  		},
   115  	}
   116  	regionWithPER    = "dal10"
   117  	regionWithoutPER = "foo99"
   118  	regionPERUnknown = "bah77"
   119  	mapWithPERFalse  = map[string]bool{
   120  		"disaster-recover-site": true,
   121  		"power-edge-router":     false,
   122  		"vpn-connections":       true,
   123  	}
   124  	mapWithPERTrue = map[string]bool{
   125  		"disaster-recover-site": true,
   126  		"power-edge-router":     true,
   127  		"vpn-connections":       true,
   128  	}
   129  	mapPERUnknown = map[string]bool{
   130  		"disaster-recover-site": true,
   131  		"power-vpn-connections": false,
   132  	}
   133  	defaultSysType           = "s922"
   134  	newSysType               = "s1022"
   135  	invalidRegion            = "foo"
   136  	validServiceInstanceGUID = ""
   137  )
   138  
   139  func validInstallConfig() *types.InstallConfig {
   140  	return &types.InstallConfig{
   141  		ObjectMeta: metav1.ObjectMeta{
   142  			Name: validClusterName,
   143  		},
   144  		BaseDomain: validBaseDomain,
   145  		Networking: &types.Networking{
   146  			MachineNetwork: []types.MachineNetworkEntry{
   147  				{CIDR: *ipnet.MustParseCIDR(validCIDR)},
   148  			},
   149  		},
   150  		Publish: types.ExternalPublishingStrategy,
   151  		Platform: types.Platform{
   152  			PowerVS: validMinimalPlatform(),
   153  		},
   154  		ControlPlane: &types.MachinePool{
   155  			Architecture: "ppc64le",
   156  		},
   157  		Compute: []types.MachinePool{{
   158  			Architecture: "ppc64le",
   159  		}},
   160  	}
   161  }
   162  
   163  func validMinimalPlatform() *powervstypes.Platform {
   164  	return &powervstypes.Platform{
   165  		PowerVSResourceGroup: validPowerVSResourceGroup,
   166  		Region:               validRegion,
   167  		ServiceInstanceGUID:  validServiceInstanceGUID,
   168  		UserID:               validUserID,
   169  		Zone:                 validZone,
   170  	}
   171  }
   172  
   173  func validMachinePool() *powervstypes.MachinePool {
   174  	return &powervstypes.MachinePool{}
   175  }
   176  
   177  func TestValidate(t *testing.T) {
   178  	cases := []struct {
   179  		name     string
   180  		edits    editFunctions
   181  		errorMsg string
   182  	}{
   183  		{
   184  			name:     "valid install config",
   185  			edits:    editFunctions{},
   186  			errorMsg: "",
   187  		},
   188  		{
   189  			name:     "invalid architecture",
   190  			edits:    editFunctions{invalidArchitecture},
   191  			errorMsg: `^controlPlane.architecture\: Unsupported value\: \"ppc64\"\: supported values: \"ppc64le\"`,
   192  		},
   193  		{
   194  			name:     "invalid machine pool CIDR",
   195  			edits:    editFunctions{invalidMachinePoolCIDR},
   196  			errorMsg: `Networking.MachineNetwork.CIDR: Invalid value: "192.168.0.0/16": Machine Pool CIDR must be /24.`,
   197  		},
   198  		{
   199  			name:     "valid machine pool CIDR",
   200  			edits:    editFunctions{validMachinePoolCIDR},
   201  			errorMsg: "",
   202  		},
   203  	}
   204  
   205  	mockCtrl := gomock.NewController(t)
   206  	defer mockCtrl.Finish()
   207  
   208  	for _, tc := range cases {
   209  		t.Run(tc.name, func(t *testing.T) {
   210  			editedInstallConfig := validInstallConfig()
   211  			for _, edit := range tc.edits {
   212  				edit(editedInstallConfig)
   213  			}
   214  
   215  			aggregatedErrors := powervs.Validate(editedInstallConfig)
   216  			if tc.errorMsg != "" {
   217  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   218  			} else {
   219  				assert.NoError(t, aggregatedErrors)
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func TestValidatePreExistingPublicDNS(t *testing.T) {
   226  	cases := []struct {
   227  		name     string
   228  		edits    editFunctions
   229  		errorMsg string
   230  	}{
   231  		{
   232  			name:     "no pre-existing DNS records",
   233  			errorMsg: "",
   234  		},
   235  		{
   236  			name:     "pre-existing DNS records",
   237  			errorMsg: `^\[baseDomain\: Duplicate value\: \"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\", baseDomain\: Duplicate value\: \"record api-int\.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\"\]$`,
   238  		},
   239  		{
   240  			name:     "cannot get zone ID",
   241  			errorMsg: `^baseDomain: Internal error$`,
   242  		},
   243  		{
   244  			name:     "cannot get DNS records",
   245  			errorMsg: `^baseDomain: Internal error$`,
   246  		},
   247  	}
   248  	setMockEnvVars()
   249  
   250  	mockCtrl := gomock.NewController(t)
   251  	defer mockCtrl.Finish()
   252  
   253  	powervsClient := mock.NewMockAPI(mockCtrl)
   254  	metadata := mock.NewMockMetadataAPI(mockCtrl)
   255  
   256  	dnsRecordNames := [...]string{fmt.Sprintf("api.%s.%s", validClusterName, validBaseDomain), fmt.Sprintf("api-int.%s.%s", validClusterName, validBaseDomain)}
   257  
   258  	// Mock common to all tests
   259  	metadata.EXPECT().CISInstanceCRN(gomock.Any()).Return(validCISInstanceCRN, nil).AnyTimes()
   260  
   261  	// Mocks: no pre-existing DNS records
   262  	powervsClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return(validDNSZoneID, nil)
   263  	for _, dnsRecordName := range dnsRecordNames {
   264  		powervsClient.EXPECT().GetDNSRecordsByName(gomock.Any(), validCISInstanceCRN, validDNSZoneID, dnsRecordName, types.ExternalPublishingStrategy).Return(noDNSRecordsResponse, nil)
   265  	}
   266  
   267  	// Mocks: pre-existing DNS records
   268  	powervsClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return(validDNSZoneID, nil)
   269  	for _, dnsRecordName := range dnsRecordNames {
   270  		powervsClient.EXPECT().GetDNSRecordsByName(gomock.Any(), validCISInstanceCRN, validDNSZoneID, dnsRecordName, types.ExternalPublishingStrategy).Return(existingDNSRecordsResponse, nil)
   271  	}
   272  
   273  	// Mocks: cannot get zone ID
   274  	powervsClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return("", fmt.Errorf(""))
   275  
   276  	// Mocks: cannot get DNS records
   277  	powervsClient.EXPECT().GetDNSZoneIDByName(gomock.Any(), validBaseDomain, types.ExternalPublishingStrategy).Return(validDNSZoneID, nil)
   278  	for _, dnsRecordName := range dnsRecordNames {
   279  		powervsClient.EXPECT().GetDNSRecordsByName(gomock.Any(), validCISInstanceCRN, validDNSZoneID, dnsRecordName, types.ExternalPublishingStrategy).Return(nil, fmt.Errorf(""))
   280  	}
   281  
   282  	// Run tests
   283  	for _, tc := range cases {
   284  		t.Run(tc.name, func(t *testing.T) {
   285  			aggregatedErrors := powervs.ValidatePreExistingDNS(powervsClient, validInstallConfig(), metadata)
   286  			if tc.errorMsg != "" {
   287  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   288  			} else {
   289  				assert.NoError(t, aggregatedErrors)
   290  			}
   291  		})
   292  	}
   293  }
   294  
   295  func TestValidateCustomVPCSettings(t *testing.T) {
   296  	cases := []struct {
   297  		name     string
   298  		edits    editFunctions
   299  		errorMsg string
   300  	}{
   301  		{
   302  			name: "invalid VPC region supplied alone",
   303  			edits: editFunctions{
   304  				func(ic *types.InstallConfig) {
   305  					ic.Platform.PowerVS.VPCRegion = invalidVPCRegion
   306  				},
   307  			},
   308  			errorMsg: fmt.Sprintf(`VPC.vpcRegion: Not found: "%s"`, invalidVPCRegion),
   309  		},
   310  		{
   311  			name: "valid VPC region supplied alone",
   312  			edits: editFunctions{
   313  				func(ic *types.InstallConfig) {
   314  					ic.Platform.PowerVS.VPCRegion = validVPCRegion
   315  				},
   316  			},
   317  			errorMsg: "",
   318  		},
   319  		{
   320  			name: "invalid VPC name supplied, without VPC region, not found near PowerVS region",
   321  			edits: editFunctions{
   322  				func(ic *types.InstallConfig) {
   323  					ic.Platform.PowerVS.VPCName = invalidVPC
   324  				},
   325  			},
   326  			errorMsg: fmt.Sprintf(`VPC.vpcName: Not found: "%s"`, invalidVPC),
   327  		},
   328  		{
   329  			name: "valid VPC name supplied, without VPC region, but found close to PowerVS region",
   330  			edits: editFunctions{
   331  				setValidVPCName,
   332  			},
   333  			errorMsg: "",
   334  		},
   335  		{
   336  			name: "valid VPC name, with invalid VPC region",
   337  			edits: editFunctions{
   338  				setValidVPCName,
   339  				func(ic *types.InstallConfig) {
   340  					ic.Platform.PowerVS.VPCRegion = invalidVPCRegion
   341  				},
   342  			},
   343  			errorMsg: "VPC.vpcRegion: Internal error: unknown region",
   344  		},
   345  		{
   346  			name: "valid VPC name, valid VPC region",
   347  			edits: editFunctions{
   348  				setValidVPCName,
   349  				setValidVPCRegion,
   350  			},
   351  			errorMsg: "",
   352  		},
   353  		{
   354  			name: "VPC subnet supplied, without vpcName",
   355  			edits: editFunctions{
   356  				func(ic *types.InstallConfig) {
   357  					ic.Platform.PowerVS.VPCSubnets = []string{validVPCSubnet}
   358  				},
   359  			},
   360  			errorMsg: `VPC.vpcSubnets: Invalid value: "null": invalid without vpcName`,
   361  		},
   362  		{
   363  			name: "VPC found, but not subnet",
   364  			edits: editFunctions{
   365  				setValidVPCName,
   366  				func(ic *types.InstallConfig) {
   367  					ic.Platform.PowerVS.VPCSubnets = []string{invalidVPCSubnet}
   368  				},
   369  			},
   370  			errorMsg: "VPC.vpcSubnets: Internal error",
   371  		},
   372  		{
   373  			name: "VPC found, subnet found as well, but not attached to the VPC",
   374  			edits: editFunctions{
   375  				setValidVPCName,
   376  				func(ic *types.InstallConfig) {
   377  					ic.Platform.PowerVS.VPCSubnets = []string{wrongVPCSubnet}
   378  				},
   379  			},
   380  			errorMsg: `VPC.vpcSubnets: Invalid value: "null": not attached to VPC`,
   381  		},
   382  		{
   383  			name: "region specified, VPC found, subnet found, and properly attached",
   384  			edits: editFunctions{
   385  				setValidVPCName,
   386  				setValidVPCRegion,
   387  				func(ic *types.InstallConfig) {
   388  					ic.Platform.PowerVS.VPCSubnets = []string{validVPCSubnet}
   389  				},
   390  			},
   391  			errorMsg: "",
   392  		},
   393  	}
   394  	setMockEnvVars()
   395  
   396  	mockCtrl := gomock.NewController(t)
   397  	defer mockCtrl.Finish()
   398  
   399  	powervsClient := mock.NewMockAPI(mockCtrl)
   400  
   401  	// Mocks: invalid VPC region only
   402  	// nothing to mock
   403  
   404  	// Mocks: valid VPC region only
   405  	// nothing to mock
   406  
   407  	// Mocks: invalid VPC name results in error
   408  	powervsClient.EXPECT().GetVPCs(gomock.Any(), validVPCRegion).Return(validVPCs, nil)
   409  
   410  	// Mocks: valid VPC name only, no issues
   411  	powervsClient.EXPECT().GetVPCs(gomock.Any(), validVPCRegion).Return(validVPCs, nil)
   412  
   413  	// Mocks: valid VPC name, invalid VPC region
   414  	powervsClient.EXPECT().GetVPCs(gomock.Any(), invalidVPCRegion).Return(nil, fmt.Errorf("unknown region"))
   415  
   416  	// Mocks: valid VPC name, valid VPC region, all good
   417  	powervsClient.EXPECT().GetVPCs(gomock.Any(), validVPCRegion).Return(validVPCs, nil)
   418  
   419  	// Mocks: subnet specified, without vpcName, invalid
   420  	// nothing to mock
   421  
   422  	// Mocks: valid VPC name, but Subnet not found
   423  	powervsClient.EXPECT().GetVPCs(gomock.Any(), validVPCRegion).Return(validVPCs, nil)
   424  	powervsClient.EXPECT().GetSubnetByName(gomock.Any(), invalidVPCSubnet, validVPCRegion).Return(nil, fmt.Errorf(""))
   425  
   426  	// Mocks: valid VPC name, but wrong Subnet (present, but not attached)
   427  	powervsClient.EXPECT().GetVPCs(gomock.Any(), validVPCRegion).Return(validVPCs, nil)
   428  	powervsClient.EXPECT().GetSubnetByName(gomock.Any(), wrongVPCSubnet, validVPCRegion).Return(wrongSubnet, nil)
   429  
   430  	// Mocks: region specified, valid VPC, valid region, valid Subnet, all good
   431  	powervsClient.EXPECT().GetVPCs(gomock.Any(), validVPCRegion).Return(validVPCs, nil)
   432  	powervsClient.EXPECT().GetSubnetByName(gomock.Any(), validVPCSubnet, validVPCRegion).Return(validSubnet, nil)
   433  
   434  	// Run tests
   435  	for _, tc := range cases {
   436  		t.Run(tc.name, func(t *testing.T) {
   437  			editedInstallConfig := validInstallConfig()
   438  			for _, edit := range tc.edits {
   439  				edit(editedInstallConfig)
   440  			}
   441  
   442  			aggregatedErrors := powervs.ValidateCustomVPCSetup(powervsClient, editedInstallConfig)
   443  			if tc.errorMsg != "" {
   444  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   445  			} else {
   446  				assert.NoError(t, aggregatedErrors)
   447  			}
   448  		})
   449  	}
   450  }
   451  
   452  func createControlPlanes(numControlPlanes int, controlPlane *machinev1.PowerVSMachineProviderConfig) []machinev1beta1.Machine {
   453  	controlPlanes := make([]machinev1beta1.Machine, numControlPlanes)
   454  
   455  	for i := range controlPlanes {
   456  		masterName := fmt.Sprintf("rdr-hamzy-test3-syd04-zwmgs-master-%d", i)
   457  		controlPlanes[i].TypeMeta = metav1.TypeMeta{
   458  			Kind:       "Machine",
   459  			APIVersion: "machine.openshift.io/v1beta1",
   460  		}
   461  		controlPlanes[i].ObjectMeta = metav1.ObjectMeta{
   462  			Name:      masterName,
   463  			Namespace: "openshift-machine-api",
   464  			Labels:    make(map[string]string),
   465  		}
   466  		controlPlanes[i].Labels["machine.openshift.io/cluster-api-cluster"] = "rdr-hamzy-test3-syd04-zwmgs"
   467  		controlPlanes[i].Labels["machine.openshift.io/cluster-api-machine-role"] = "master"
   468  		controlPlanes[i].Labels["machine.openshift.io/cluster-api-machine-type"] = "master"
   469  
   470  		controlPlanes[i].Spec.ProviderSpec = machinev1beta1.ProviderSpec{
   471  			Value: &runtime.RawExtension{
   472  				Raw:    nil,
   473  				Object: controlPlane,
   474  			},
   475  		}
   476  	}
   477  
   478  	return controlPlanes
   479  }
   480  
   481  func createComputes(numComputes int32, compute *machinev1.PowerVSMachineProviderConfig) []machinev1beta1.MachineSet {
   482  	computes := make([]machinev1beta1.MachineSet, 1)
   483  
   484  	computes[0].Spec.Replicas = &numComputes
   485  
   486  	computes[0].Spec.Template.Spec.ProviderSpec = machinev1beta1.ProviderSpec{
   487  		Value: &runtime.RawExtension{
   488  			Raw:    nil,
   489  			Object: compute,
   490  		},
   491  	}
   492  
   493  	return computes
   494  }
   495  
   496  func TestValidatePERAvailability(t *testing.T) {
   497  	cases := []struct {
   498  		name     string
   499  		edits    editFunctions
   500  		errorMsg string
   501  	}{
   502  		{
   503  			name: "Region without PER",
   504  			edits: editFunctions{
   505  				func(ic *types.InstallConfig) {
   506  					ic.Platform.PowerVS.Zone = regionWithoutPER
   507  				},
   508  			},
   509  			errorMsg: fmt.Sprintf("power-edge-router is not available at: %s", regionWithoutPER),
   510  		},
   511  		{
   512  			name: "Region with PER",
   513  			edits: editFunctions{
   514  				func(ic *types.InstallConfig) {
   515  					ic.Platform.PowerVS.Zone = regionWithPER
   516  				},
   517  			},
   518  			errorMsg: "",
   519  		},
   520  		{
   521  			name: "Region with no PER availability info",
   522  			edits: editFunctions{
   523  				func(ic *types.InstallConfig) {
   524  					ic.Platform.PowerVS.Zone = regionPERUnknown
   525  				},
   526  			},
   527  			errorMsg: fmt.Sprintf("power-edge-router capability unknown at: %s", regionPERUnknown),
   528  		},
   529  	}
   530  	setMockEnvVars()
   531  
   532  	mockCtrl := gomock.NewController(t)
   533  	defer mockCtrl.Finish()
   534  
   535  	powervsClient := mock.NewMockAPI(mockCtrl)
   536  
   537  	// Mocks: PER-absent region results in false
   538  	powervsClient.EXPECT().GetDatacenterCapabilities(gomock.Any(), regionWithoutPER).Return(mapWithPERFalse, nil)
   539  
   540  	// Mocks: PER-enabled region results in true
   541  	powervsClient.EXPECT().GetDatacenterCapabilities(gomock.Any(), regionWithPER).Return(mapWithPERTrue, nil)
   542  
   543  	// Mocks: PER-unknown region results in false
   544  	powervsClient.EXPECT().GetDatacenterCapabilities(gomock.Any(), regionPERUnknown).Return(mapPERUnknown, nil)
   545  
   546  	// Run tests
   547  	for _, tc := range cases {
   548  		t.Run(tc.name, func(t *testing.T) {
   549  			editedInstallConfig := validInstallConfig()
   550  			for _, edit := range tc.edits {
   551  				edit(editedInstallConfig)
   552  			}
   553  
   554  			aggregatedErrors := powervs.ValidatePERAvailability(powervsClient, editedInstallConfig)
   555  			if tc.errorMsg != "" {
   556  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   557  			} else {
   558  				assert.NoError(t, aggregatedErrors)
   559  			}
   560  		})
   561  	}
   562  }
   563  
   564  func TestValidateSystemTypeForRegion(t *testing.T) {
   565  	cases := []struct {
   566  		name     string
   567  		edits    editFunctions
   568  		errorMsg string
   569  	}{
   570  		{
   571  			name: "Unknown Region specified",
   572  			edits: editFunctions{
   573  				func(ic *types.InstallConfig) {
   574  					ic.Platform.PowerVS.Region = invalidRegion
   575  					ic.ControlPlane.Platform.PowerVS = validMachinePool()
   576  					ic.ControlPlane.Platform.PowerVS.SysType = defaultSysType
   577  				},
   578  			},
   579  			errorMsg: fmt.Sprintf("failed to obtain available SysTypes for: %s", invalidRegion),
   580  		},
   581  		{
   582  			name: "No Platform block",
   583  			edits: editFunctions{
   584  				func(ic *types.InstallConfig) {
   585  					ic.ControlPlane.Platform.PowerVS = nil
   586  				},
   587  			},
   588  			errorMsg: "",
   589  		},
   590  		{
   591  			name: "Structure present, but no SysType specified",
   592  			edits: editFunctions{
   593  				func(ic *types.InstallConfig) {
   594  					ic.ControlPlane.Platform.PowerVS = validMachinePool()
   595  				},
   596  			},
   597  			errorMsg: "",
   598  		},
   599  		{
   600  			name: "Unavailable SysType specified for Dallas Region",
   601  			edits: editFunctions{
   602  				func(ic *types.InstallConfig) {
   603  					ic.Platform.PowerVS.Region = validRegion
   604  					ic.ControlPlane.Platform.PowerVS = validMachinePool()
   605  					ic.ControlPlane.Platform.PowerVS.SysType = newSysType
   606  				},
   607  			},
   608  			errorMsg: fmt.Sprintf("%s is not available in: %s", newSysType, validRegion),
   609  		},
   610  		{
   611  			name: "Good Region/SysType combo specified",
   612  			edits: editFunctions{
   613  				func(ic *types.InstallConfig) {
   614  					ic.Platform.PowerVS.Region = validRegion
   615  					ic.ControlPlane.Platform.PowerVS = validMachinePool()
   616  					ic.ControlPlane.Platform.PowerVS.SysType = defaultSysType
   617  				},
   618  			},
   619  			errorMsg: "",
   620  		},
   621  	}
   622  	setMockEnvVars()
   623  
   624  	mockCtrl := gomock.NewController(t)
   625  	defer mockCtrl.Finish()
   626  
   627  	powervsClient := mock.NewMockAPI(mockCtrl)
   628  
   629  	// Run tests
   630  	for _, tc := range cases {
   631  		t.Run(tc.name, func(t *testing.T) {
   632  			editedInstallConfig := validInstallConfig()
   633  			for _, edit := range tc.edits {
   634  				edit(editedInstallConfig)
   635  			}
   636  
   637  			aggregatedErrors := powervs.ValidateSystemTypeForRegion(powervsClient, editedInstallConfig)
   638  			if tc.errorMsg != "" {
   639  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   640  			} else {
   641  				assert.NoError(t, aggregatedErrors)
   642  			}
   643  		})
   644  	}
   645  }
   646  
   647  func TestValidateServiceInstance(t *testing.T) {
   648  	cases := []struct {
   649  		name     string
   650  		edits    editFunctions
   651  		errorMsg string
   652  	}{
   653  		{
   654  			name:     "valid install config",
   655  			edits:    editFunctions{},
   656  			errorMsg: "",
   657  		},
   658  		{
   659  			name: "invalid install config",
   660  			edits: editFunctions{
   661  				func(ic *types.InstallConfig) {
   662  					ic.Platform.PowerVS.ServiceInstanceGUID = "invalid-uuid"
   663  				},
   664  			},
   665  			errorMsg: "platform:powervs:serviceInstanceGUID has an invalid guid",
   666  		},
   667  	}
   668  	setMockEnvVars()
   669  
   670  	mockCtrl := gomock.NewController(t)
   671  	defer mockCtrl.Finish()
   672  
   673  	powervsClient := mock.NewMockAPI(mockCtrl)
   674  
   675  	// FIX: Unexpected call to *mock.MockAPI.ListServiceInstances([context.TODO.WithDeadline(2023-12-02 08:38:15.542340268 -0600 CST m=+300.012357408 [4m59.999979046s])]) at validation.go:289 because: there are no expected calls of the method "ListServiceInstances" for that receiver
   676  	powervsClient.EXPECT().ListServiceInstances(gomock.Any()).AnyTimes()
   677  
   678  	// Run tests
   679  	for _, tc := range cases {
   680  		t.Run(tc.name, func(t *testing.T) {
   681  			editedInstallConfig := validInstallConfig()
   682  			for _, edit := range tc.edits {
   683  				edit(editedInstallConfig)
   684  			}
   685  
   686  			aggregatedErrors := powervs.ValidateServiceInstance(powervsClient, editedInstallConfig)
   687  			if tc.errorMsg != "" {
   688  				assert.Regexp(t, tc.errorMsg, aggregatedErrors)
   689  			} else {
   690  				assert.NoError(t, aggregatedErrors)
   691  			}
   692  		})
   693  	}
   694  }
   695  
   696  func setMockEnvVars() {
   697  	os.Setenv("POWERVS_AUTH_FILEPATH", "./tmp/powervs/config.json")
   698  	os.Setenv("IBMID", "foo")
   699  	os.Setenv("IC_API_KEY", "foo")
   700  	os.Setenv("IBMCLOUD_REGION", "foo")
   701  	os.Setenv("IBMCLOUD_ZONE", "foo")
   702  }