github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/azure/validation_test.go (about) 1 package azure 2 3 import ( 4 "fmt" 5 "net" 6 "testing" 7 8 azres "github.com/Azure/azure-sdk-for-go/profiles/2018-03-01/resources/mgmt/resources" 9 azsubs "github.com/Azure/azure-sdk-for-go/profiles/2018-03-01/resources/mgmt/subscriptions" 10 aznetwork "github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/network/mgmt/network" 11 azenc "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" 12 "github.com/Azure/go-autorest/autorest/to" 13 "github.com/golang/mock/gomock" 14 "github.com/stretchr/testify/assert" 15 "k8s.io/apimachinery/pkg/util/sets" 16 "k8s.io/apimachinery/pkg/util/validation/field" 17 18 "github.com/openshift/installer/pkg/asset/installconfig/azure/mock" 19 "github.com/openshift/installer/pkg/ipnet" 20 "github.com/openshift/installer/pkg/types" 21 "github.com/openshift/installer/pkg/types/azure" 22 ) 23 24 type editFunctions []func(ic *types.InstallConfig) 25 26 var ( 27 validVirtualNetwork = "valid-virtual-network" 28 validNetworkResourceGroup = "valid-network-resource-group" 29 validRegion = "centralus" 30 validRegionsList = []string{"centralus", "westus", "australiacentral2"} 31 resourcesCapableRegionsList = []string{"centralus", "westus"} 32 validComputeSubnet = "valid-compute-subnet" 33 validControlPlaneSubnet = "valid-controlplane-subnet" 34 validCIDR = "10.0.0.0/16" 35 validComputeSubnetCIDR = "10.0.0.0/24" 36 validControlPlaneSubnetCIDR = "10.0.32.0/24" 37 validResourceGroupNamespace = "Microsoft.Resources" 38 validResourceGroupResourceType = "resourceGroups" 39 validResourceSkuRegions = "southeastasia" 40 41 vmCapabilities = map[string]map[string]string{ 42 "Standard_D8s_v3": {"vCPUsAvailable": "4", "MemoryGB": "16", "PremiumIO": "True", "HyperVGenerations": "V1,V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "x64"}, 43 "Standard_D4s_v3": {"vCPUsAvailable": "4", "MemoryGB": "32", "PremiumIO": "True", "HyperVGenerations": "V1", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "x64"}, 44 "Standard_A1_v2": {"vCPUsAvailable": "1", "MemoryGB": "2", "PremiumIO": "True", "HyperVGenerations": "V1,V2", "AcceleratedNetworkingEnabled": "False", "CpuArchitectureType": "x64"}, 45 "Standard_D2_v4": {"vCPUsAvailable": "2", "MemoryGB": "8", "PremiumIO": "True", "HyperVGenerations": "V1,V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "x64"}, 46 "Standard_D4_v4": {"vCPUsAvailable": "4", "MemoryGB": "16", "PremiumIO": "False", "HyperVGenerations": "V1,V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "x64"}, 47 "Standard_D2s_v3": {"vCPUsAvailable": "4", "MemoryGB": "16", "PremiumIO": "True", "HyperVGenerations": "V1,V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "x64"}, 48 "Standard_Dc4_v4": {"vCPUsAvailable": "4", "MemoryGB": "16", "PremiumIO": "True", "HyperVGenerations": "V2", "CpuArchitectureType": "x64"}, 49 "Standard_B4ms": {"vCPUsAvailable": "4", "MemoryGB": "16", "PremiumIO": "True", "HyperVGenerations": "V1,V2", "AcceleratedNetworkingEnabled": "False", "CpuArchitectureType": "x64"}, 50 "Standard_D8ps_v5": {"vCPUsAvailable": "8", "MemoryGB": "32", "PremiumIO": "True", "HyperVGenerations": "V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "Arm64", "TrustedLaunchDisabled": "True"}, 51 "Standard_D4ps_v5": {"vCPUsAvailable": "4", "MemoryGB": "16", "PremiumIO": "True", "HyperVGenerations": "V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "Arm64", "TrustedLaunchDisabled": "True"}, 52 "Standard_DC8ads_v5": {"vCPUsAvailable": "8", "MemoryGB": "32", "PremiumIO": "True", "HyperVGenerations": "V2", "AcceleratedNetworkingEnabled": "False", "CpuArchitectureType": "x64", "ConfidentialComputingType": "SNP"}, 53 "Standard_DC8s_v3": {"vCPUsAvailable": "8", "MemoryGB": "32", "PremiumIO": "True", "HyperVGenerations": "V2", "AcceleratedNetworkingEnabled": "True", "CpuArchitectureType": "x64", "ConfidentialComputingType": "SGX"}, 54 } 55 56 instanceTypeSku = func() []*azenc.ResourceSku { 57 instances := make([]*azenc.ResourceSku, 0, len(vmCapabilities)) 58 for typeName, capsMap := range vmCapabilities { 59 capabilities := make([]azenc.ResourceSkuCapabilities, 0, len(capsMap)) 60 for name, value := range capsMap { 61 capabilities = append(capabilities, azenc.ResourceSkuCapabilities{ 62 Name: to.StringPtr(name), Value: to.StringPtr(value), 63 }) 64 } 65 instances = append(instances, &azenc.ResourceSku{ 66 Name: to.StringPtr(typeName), Capabilities: &capabilities, 67 }) 68 } 69 return instances 70 }() 71 72 validInstanceTypes = func(ic *types.InstallConfig) { 73 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D4s_v3" 74 ic.ControlPlane.Platform.Azure.InstanceType = "Standard_D8s_v3" 75 ic.Compute[0].Platform.Azure.InstanceType = "Standard_D4s_v3" 76 } 77 78 validArm64InstanceTypes = func(ic *types.InstallConfig) { 79 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D4ps_v5" 80 ic.ControlPlane.Architecture = types.ArchitectureARM64 81 ic.ControlPlane.Platform.Azure.InstanceType = "Standard_D8ps_v5" 82 ic.Compute[0].Architecture = types.ArchitectureARM64 83 ic.Compute[0].Platform.Azure.InstanceType = "Standard_D4ps_v5" 84 } 85 86 invalidArchInstanceTypes = func(ic *types.InstallConfig) { 87 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D4ps_v5" 88 ic.ControlPlane.Platform.Azure.InstanceType = "Standard_D8ps_v5" 89 ic.Compute[0].Platform.Azure.InstanceType = "Standard_D4ps_v5" 90 } 91 92 invalidateDefaultInstanceTypes = func(ic *types.InstallConfig) { 93 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_A1_v2" 94 } 95 96 invalidateControlPlaneInstanceTypes = func(ic *types.InstallConfig) { 97 ic.ControlPlane.Platform.Azure.InstanceType = "Standard_A1_v2" 98 } 99 100 invalidateComputeInstanceTypes = func(ic *types.InstallConfig) { 101 ic.Compute[0].Platform.Azure.InstanceType = "Standard_A1_v2" 102 } 103 104 undefinedDefaultInstanceTypes = func(ic *types.InstallConfig) { 105 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Dne_D2_v4" 106 } 107 108 ultraSSDAvailableInstanceTypes = func(ic *types.InstallConfig) { 109 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D8s_v3" 110 } 111 112 validVMNetworkingInstanceTypes = func(ic *types.InstallConfig) { 113 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D8s_v3" 114 } 115 116 invalidVMNetworkingIstanceTypes = func(ic *types.InstallConfig) { 117 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_B4ms" 118 } 119 120 validConfidentialVMInstanceTypes = func(ic *types.InstallConfig) { 121 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_DC8ads_v5" 122 } 123 124 invalidConfidentialVMInstanceTypes = func(ic *types.InstallConfig) { 125 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_B4ms" 126 } 127 128 invalidConfidentialVMSGXInstanceTypes = func(ic *types.InstallConfig) { 129 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_DC8s_v3" 130 } 131 132 validTrustedLaunchInstanceTypes = func(ic *types.InstallConfig) { 133 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D8s_v3" 134 } 135 136 invalidTrustedLaunchInstanceTypes = func(ic *types.InstallConfig) { 137 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D8ps_v5" 138 } 139 140 invalidateMachineCIDR = func(ic *types.InstallConfig) { 141 _, newCidr, _ := net.ParseCIDR("192.168.111.0/24") 142 ic.MachineNetwork = []types.MachineNetworkEntry{ 143 {CIDR: ipnet.IPNet{IPNet: *newCidr}}, 144 } 145 } 146 invalidResourceSkuRegion = "centralus" 147 148 invalidateVirtualNetwork = func(ic *types.InstallConfig) { ic.Azure.VirtualNetwork = "invalid-virtual-network" } 149 invalidateComputeSubnet = func(ic *types.InstallConfig) { ic.Azure.ComputeSubnet = "invalid-compute-subnet" } 150 invalidateControlPlaneSubnet = func(ic *types.InstallConfig) { ic.Azure.ControlPlaneSubnet = "invalid-controlplane-subnet" } 151 invalidateRegion = func(ic *types.InstallConfig) { ic.Azure.Region = "neverland" } 152 invalidateRegionCapabilities = func(ic *types.InstallConfig) { ic.Azure.Region = "australiacentral2" } 153 invalidateRegionLetterCase = func(ic *types.InstallConfig) { ic.Azure.Region = "Central US" } 154 removeVirtualNetwork = func(ic *types.InstallConfig) { ic.Azure.VirtualNetwork = "" } 155 removeSubnets = func(ic *types.InstallConfig) { ic.Azure.ComputeSubnet, ic.Azure.ControlPlaneSubnet = "", "" } 156 premiumDiskCompute = func(ic *types.InstallConfig) { ic.Compute[0].Platform.Azure.OSDisk.DiskType = "Premium_LRS" } 157 nonpremiumInstanceTypeDiskCompute = func(ic *types.InstallConfig) { ic.Compute[0].Platform.Azure.InstanceType = "Standard_D4_v4" } 158 premiumDiskControlPlane = func(ic *types.InstallConfig) { ic.ControlPlane.Platform.Azure.OSDisk.DiskType = "Premium_LRS" } 159 nonpremiumInstanceTypeDiskControlPlane = func(ic *types.InstallConfig) { ic.ControlPlane.Platform.Azure.InstanceType = "Standard_D4_v4" } 160 // premiumDiskDefault = func(ic *types.InstallConfig) { ic.Azure.DefaultMachinePlatform.OSDisk.DiskType = "Premium_LRS" } 161 nonpremiumInstanceTypeDiskDefault = func(ic *types.InstallConfig) { ic.Azure.DefaultMachinePlatform.InstanceType = "Standard_D4_v4" } 162 enabledSSDCapabilityControlPlane = func(ic *types.InstallConfig) { ic.ControlPlane.Platform.Azure.UltraSSDCapability = "Enabled" } 163 enabledSSDCapabilityCompute = func(ic *types.InstallConfig) { ic.Compute[0].Platform.Azure.UltraSSDCapability = "Enabled" } 164 enabledSSDCapabilityDefault = func(ic *types.InstallConfig) { ic.Azure.DefaultMachinePlatform.UltraSSDCapability = "Enabled" } 165 vmNetworkingTypeAcceleratedControlPlane = func(ic *types.InstallConfig) { ic.ControlPlane.Platform.Azure.VMNetworkingType = "Accelerated" } 166 vmNetworkingTypeAcceleratedCompute = func(ic *types.InstallConfig) { ic.Compute[0].Platform.Azure.VMNetworkingType = "Accelerated" } 167 vmNetworkingTypeAcceleratedDefault = func(ic *types.InstallConfig) { ic.Azure.DefaultMachinePlatform.VMNetworkingType = "Accelerated" } 168 169 securityTypeConfidentialVMDefaultMachinePlatform = func(ic *types.InstallConfig) { 170 ic.Azure.DefaultMachinePlatform.Settings = &azure.SecuritySettings{SecurityType: "ConfidentialVM"} 171 } 172 securityTypeConfidentialVMControlPlane = func(ic *types.InstallConfig) { 173 ic.ControlPlane.Platform.Azure.Settings = &azure.SecuritySettings{SecurityType: "ConfidentialVM"} 174 } 175 securityTypeConfidentialVMCompute = func(ic *types.InstallConfig) { 176 ic.Compute[0].Platform.Azure.Settings = &azure.SecuritySettings{SecurityType: "ConfidentialVM"} 177 } 178 securityTypeTrustedLaunchDefaultMachinePlatform = func(ic *types.InstallConfig) { 179 ic.Azure.DefaultMachinePlatform.Settings = &azure.SecuritySettings{SecurityType: "TrustedLaunch"} 180 } 181 securityTypeTrustedLaunchControlPlane = func(ic *types.InstallConfig) { 182 ic.ControlPlane.Platform.Azure.Settings = &azure.SecuritySettings{SecurityType: "TrustedLaunch"} 183 } 184 securityTypeTrustedLaunchCompute = func(ic *types.InstallConfig) { 185 ic.Compute[0].Platform.Azure.Settings = &azure.SecuritySettings{SecurityType: "TrustedLaunch"} 186 } 187 188 virtualNetworkAPIResult = &aznetwork.VirtualNetwork{ 189 Name: &validVirtualNetwork, 190 } 191 computeSubnetAPIResult = &aznetwork.Subnet{ 192 Name: &validComputeSubnet, 193 SubnetPropertiesFormat: &aznetwork.SubnetPropertiesFormat{ 194 AddressPrefix: &validComputeSubnetCIDR, 195 }, 196 } 197 controlPlaneSubnetAPIResult = &aznetwork.Subnet{ 198 Name: &validControlPlaneSubnet, 199 SubnetPropertiesFormat: &aznetwork.SubnetPropertiesFormat{ 200 AddressPrefix: &validControlPlaneSubnetCIDR, 201 }, 202 } 203 locationsAPIResult = func() *[]azsubs.Location { 204 r := []azsubs.Location{} 205 for i := 0; i < len(validRegionsList); i++ { 206 r = append(r, azsubs.Location{ 207 Name: &validRegionsList[i], 208 DisplayName: &validRegionsList[i], 209 }) 210 } 211 return &r 212 }() 213 214 marketplaceImageAPIResult = azenc.VirtualMachineImage{ 215 Name: to.StringPtr("VMImage"), 216 VirtualMachineImageProperties: &azenc.VirtualMachineImageProperties{ 217 HyperVGeneration: azenc.HyperVGenerationTypesV1, 218 Plan: &azenc.PurchasePlan{}, 219 }, 220 } 221 222 marketplaceImageAPIResultNoPlan = azenc.VirtualMachineImage{ 223 Name: to.StringPtr("VMImage"), 224 VirtualMachineImageProperties: &azenc.VirtualMachineImageProperties{ 225 HyperVGeneration: azenc.HyperVGenerationTypesV1, 226 }, 227 } 228 229 resourcesProviderAPIResult = &azres.Provider{ 230 Namespace: &validResourceGroupNamespace, 231 ResourceTypes: &[]azres.ProviderResourceType{ 232 { 233 ResourceType: &validResourceGroupResourceType, 234 Locations: &resourcesCapableRegionsList, 235 }, 236 }, 237 } 238 239 diskEncryptionSetID = "test-encryption-set-id" 240 diskEncryptionSetName = "test-encryption-set-name" 241 diskEncryptionSetType = "test-encryption-set-type" 242 diskEncryptionSetLocation = "disk-encryption-set-location" 243 validDiskEncryptionSetResult = &azenc.DiskEncryptionSet{ 244 ID: to.StringPtr(diskEncryptionSetID), 245 Name: to.StringPtr(diskEncryptionSetName), 246 Type: to.StringPtr(diskEncryptionSetType), 247 Location: to.StringPtr(diskEncryptionSetLocation), 248 } 249 validConfidentialVMDiskEncryptionSetResult = &azenc.DiskEncryptionSet{ 250 ID: to.StringPtr(diskEncryptionSetID), 251 Name: to.StringPtr(diskEncryptionSetName), 252 Type: to.StringPtr(diskEncryptionSetType), 253 Location: to.StringPtr(diskEncryptionSetLocation), 254 EncryptionSetProperties: &azenc.EncryptionSetProperties{EncryptionType: azenc.ConfidentialVMEncryptedWithCustomerKey}, 255 } 256 257 validDiskEncryptionSetSubscriptionID = "test-encryption-set-subscription-id" 258 validDiskEncryptionSetResourceGroup = "test-encryption-set-resource-group" 259 validDiskEncryptionSetName = "test-encryption-set-name" 260 validDiskEncryptionSetConfig = func() *azure.DiskEncryptionSet { 261 return &azure.DiskEncryptionSet{ 262 SubscriptionID: validDiskEncryptionSetSubscriptionID, 263 ResourceGroup: validDiskEncryptionSetResourceGroup, 264 Name: validDiskEncryptionSetName, 265 } 266 } 267 validConfidentialVMDiskEncryptionSetName = "test-confidential-vm-encryption-set-name" 268 validConfidentialVMDiskEncryptionSetConfig = func() *azure.DiskEncryptionSet { 269 return &azure.DiskEncryptionSet{ 270 SubscriptionID: validDiskEncryptionSetSubscriptionID, 271 ResourceGroup: validDiskEncryptionSetResourceGroup, 272 Name: validConfidentialVMDiskEncryptionSetName, 273 } 274 } 275 invalidDiskEncryptionSetName = "test-encryption-set-invalid-name" 276 invalidDiskEncryptionSetConfig = func() *azure.DiskEncryptionSet { 277 return &azure.DiskEncryptionSet{ 278 SubscriptionID: validDiskEncryptionSetSubscriptionID, 279 ResourceGroup: validDiskEncryptionSetResourceGroup, 280 Name: invalidDiskEncryptionSetName, 281 } 282 } 283 284 validOSImagePublisher = "test-publisher" 285 validOSImageOffer = "test-offer" 286 validOSImageSKU = "test-sku" 287 validOSImageVersion = "test-version" 288 noPlanOSImageSKU = "no-plan-sku" 289 invalidOSImageSKU = "bad-sku" 290 erroringOSImageSKU = "test-sku-gen2" 291 erroringLicenseTermsOSImageSKU = "erroring-license-terms" 292 unacceptedLicenseTermsOSImageSKU = "unaccepted-license-terms" 293 validOSImage = azure.OSImage{ 294 Publisher: validOSImagePublisher, 295 Offer: validOSImageOffer, 296 SKU: validOSImageSKU, 297 Version: validOSImageVersion, 298 } 299 300 validDiskEncryptionSetDefaultMachinePlatform = func(ic *types.InstallConfig) { 301 ic.Azure.DefaultMachinePlatform.OSDisk.DiskEncryptionSet = validDiskEncryptionSetConfig() 302 } 303 validDiskEncryptionSetControlPlane = func(ic *types.InstallConfig) { 304 ic.ControlPlane.Platform.Azure.OSDisk.DiskEncryptionSet = validDiskEncryptionSetConfig() 305 } 306 validDiskEncryptionSetCompute = func(ic *types.InstallConfig) { 307 ic.Compute[0].Platform.Azure.OSDisk.DiskEncryptionSet = validDiskEncryptionSetConfig() 308 } 309 invalidDiskEncryptionSetDefaultMachinePlatform = func(ic *types.InstallConfig) { 310 ic.Azure.DefaultMachinePlatform.OSDisk.DiskEncryptionSet = invalidDiskEncryptionSetConfig() 311 } 312 invalidDiskEncryptionSetControlPlane = func(ic *types.InstallConfig) { 313 ic.ControlPlane.Platform.Azure.OSDisk.DiskEncryptionSet = invalidDiskEncryptionSetConfig() 314 } 315 invalidDiskEncryptionSetCompute = func(ic *types.InstallConfig) { 316 ic.Compute[0].Platform.Azure.OSDisk.DiskEncryptionSet = invalidDiskEncryptionSetConfig() 317 } 318 319 validConfidentialVMDiskEncryptionSetDefaultMachinePlatform = func(ic *types.InstallConfig) { 320 ic.Azure.DefaultMachinePlatform.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: validConfidentialVMDiskEncryptionSetConfig()} 321 } 322 validConfidentialVMDiskEncryptionSetControlPlane = func(ic *types.InstallConfig) { 323 ic.ControlPlane.Platform.Azure.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: validConfidentialVMDiskEncryptionSetConfig()} 324 } 325 validConfidentialVMDiskEncryptionSetCompute = func(ic *types.InstallConfig) { 326 ic.Compute[0].Platform.Azure.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: validConfidentialVMDiskEncryptionSetConfig()} 327 } 328 invalidConfidentialVMDiskEncryptionSetDefaultMachinePlatform = func(ic *types.InstallConfig) { 329 ic.Azure.DefaultMachinePlatform.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: invalidDiskEncryptionSetConfig()} 330 } 331 invalidConfidentialVMDiskEncryptionSetControlPlane = func(ic *types.InstallConfig) { 332 ic.ControlPlane.Platform.Azure.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: invalidDiskEncryptionSetConfig()} 333 } 334 invalidConfidentialVMDiskEncryptionSetCompute = func(ic *types.InstallConfig) { 335 ic.Compute[0].Platform.Azure.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: invalidDiskEncryptionSetConfig()} 336 } 337 invalidTypeConfidentialVMDiskEncryptionSetDefaultMachinePlatform = func(ic *types.InstallConfig) { 338 ic.Azure.DefaultMachinePlatform.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: validDiskEncryptionSetConfig()} 339 } 340 invalidTypeConfidentialVMDiskEncryptionSetControlPlane = func(ic *types.InstallConfig) { 341 ic.ControlPlane.Platform.Azure.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: validDiskEncryptionSetConfig()} 342 } 343 invalidTypeConfidentialVMDiskEncryptionSetCompute = func(ic *types.InstallConfig) { 344 ic.Compute[0].Platform.Azure.OSDisk.SecurityProfile = &azure.VMDiskSecurityProfile{DiskEncryptionSet: validDiskEncryptionSetConfig()} 345 } 346 347 validOSImageCompute = func(ic *types.InstallConfig) { 348 ic.Compute[0].Platform.Azure.OSImage = validOSImage 349 } 350 invalidOSImageCompute = func(ic *types.InstallConfig) { 351 validOSImageCompute(ic) 352 ic.Compute[0].Platform.Azure.OSImage.SKU = invalidOSImageSKU 353 } 354 erroringLicenseTermsOSImageCompute = func(ic *types.InstallConfig) { 355 validOSImageCompute(ic) 356 ic.Compute[0].Platform.Azure.OSImage.SKU = erroringLicenseTermsOSImageSKU 357 } 358 unacceptedLicenseTermsOSImageCompute = func(ic *types.InstallConfig) { 359 validOSImageCompute(ic) 360 ic.Compute[0].Platform.Azure.OSImage.SKU = unacceptedLicenseTermsOSImageSKU 361 } 362 erroringGenerationOsImageCompute = func(ic *types.InstallConfig) { 363 validOSImageCompute(ic) 364 ic.Compute[0].Platform.Azure.OSImage.SKU = erroringOSImageSKU 365 } 366 ) 367 368 func validInstallConfig() *types.InstallConfig { 369 return &types.InstallConfig{ 370 Networking: &types.Networking{ 371 MachineNetwork: []types.MachineNetworkEntry{ 372 {CIDR: *ipnet.MustParseCIDR(validCIDR)}, 373 }, 374 }, 375 Platform: types.Platform{ 376 Azure: &azure.Platform{ 377 Region: validRegion, 378 NetworkResourceGroupName: validNetworkResourceGroup, 379 VirtualNetwork: validVirtualNetwork, 380 ComputeSubnet: validComputeSubnet, 381 ControlPlaneSubnet: validControlPlaneSubnet, 382 DefaultMachinePlatform: &azure.MachinePool{}, 383 }, 384 }, 385 ControlPlane: &types.MachinePool{ 386 Architecture: types.ArchitectureAMD64, 387 Platform: types.MachinePoolPlatform{ 388 Azure: &azure.MachinePool{}, 389 }, 390 }, 391 Compute: []types.MachinePool{{ 392 Architecture: types.ArchitectureAMD64, 393 Platform: types.MachinePoolPlatform{ 394 Azure: &azure.MachinePool{}, 395 }, 396 }}, 397 } 398 } 399 400 func TestAzureInstallConfigValidation(t *testing.T) { 401 cases := []struct { 402 name string 403 edits editFunctions 404 errorMsg string 405 }{ 406 { 407 name: "Valid virtual network & subnets", 408 edits: editFunctions{}, 409 errorMsg: "", 410 }, 411 { 412 name: "Valid install config without virtual network & subnets", 413 edits: editFunctions{removeVirtualNetwork, removeSubnets}, 414 errorMsg: "", 415 }, 416 { 417 name: "Invalid subnet range", 418 edits: editFunctions{invalidateMachineCIDR}, 419 errorMsg: "subnet .+ address prefix is outside of the specified machine networks", 420 }, 421 { 422 name: "Invalid virtual network", 423 edits: editFunctions{invalidateVirtualNetwork}, 424 errorMsg: "invalid virtual network", 425 }, 426 { 427 name: "Invalid compute subnet", 428 edits: editFunctions{invalidateComputeSubnet}, 429 errorMsg: "failed to retrieve compute subnet", 430 }, 431 { 432 name: "Invalid control plane subnet", 433 edits: editFunctions{invalidateControlPlaneSubnet}, 434 errorMsg: "failed to retrieve control plane subnet", 435 }, 436 { 437 name: "Invalid both subnets", 438 edits: editFunctions{invalidateControlPlaneSubnet, invalidateComputeSubnet}, 439 errorMsg: "failed to retrieve compute subnet", 440 }, 441 { 442 name: "Valid instance types", 443 edits: editFunctions{validInstanceTypes}, 444 errorMsg: "", 445 }, 446 { 447 name: "Valid Arm64 instance types", 448 edits: editFunctions{validArm64InstanceTypes}, 449 errorMsg: "", 450 }, 451 { 452 name: "Invalid arch instance types", 453 edits: editFunctions{invalidArchInstanceTypes}, 454 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D8ps_v5": instance type architecture 'Arm64' does not match install config architecture amd64, compute\[0\].platform.azure.type: Invalid value: "Standard_D4ps_v5": instance type architecture 'Arm64' does not match install config architecture amd64, platform.azure.defaultMachinePlatform.type: Invalid value: "Standard_D4ps_v5": instance type architecture 'Arm64' does not match install config architecture amd64\]`, 455 }, 456 { 457 name: "Invalid default machine type", 458 edits: editFunctions{invalidateDefaultInstanceTypes}, 459 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_A1_v2": instance type does not meet minimum resource requirements of 4 vCPUsAvailable, controlPlane.platform.azure.type: Invalid value: "Standard_A1_v2": instance type does not meet minimum resource requirements of 16 GB Memory, compute\[0\].platform.azure.type: Invalid value: "Standard_A1_v2": instance type does not meet minimum resource requirements of 2 vCPUsAvailable, compute\[0\].platform.azure.type: Invalid value: "Standard_A1_v2": instance type does not meet minimum resource requirements of 8 GB Memory\]`, 460 }, 461 { 462 name: "Invalid control plane instance types", 463 edits: editFunctions{invalidateControlPlaneInstanceTypes}, 464 errorMsg: `[controlPlane.platform.azure.type: Invalid value: "n1\-standard\-1": instance type does not meet minimum resource requirements of 4 vCPUs, controlPlane.platform.azure.type: Invalid value: "n1\-standard\-1": instance type does not meet minimum resource requirements of 16 GB Memory]`, 465 }, 466 { 467 name: "Undefined default instance types", 468 edits: editFunctions{undefinedDefaultInstanceTypes}, 469 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Dne_D2_v4": not found in region centralus, compute\[0\].platform.azure.type: Invalid value: "Dne_D2_v4": not found in region centralus, controlPlane.platform.azure.type: Invalid value: "Dne_D2_v4": unable to determine HyperVGeneration version\]`, 470 }, 471 { 472 name: "Invalid compute instance types", 473 edits: editFunctions{invalidateComputeInstanceTypes}, 474 errorMsg: `\[compute\[0\].platform.azure.type: Invalid value: "Standard_A1_v2": instance type does not meet minimum resource requirements of 2 vCPUsAvailable, compute\[0\].platform.azure.type: Invalid value: "Standard_A1_v2": instance type does not meet minimum resource requirements of 8 GB Memory\]`, 475 }, 476 { 477 name: "Invalid region", 478 edits: editFunctions{invalidateRegion}, 479 errorMsg: "region \"neverland\" is not valid or not available for this account$", 480 }, 481 { 482 name: "Invalid region uncapable", 483 edits: editFunctions{invalidateRegionCapabilities}, 484 errorMsg: "region \"australiacentral2\" does not support resource creation$", 485 }, 486 { 487 name: "Invalid region letter case", 488 edits: editFunctions{invalidateRegionLetterCase}, 489 errorMsg: "region \"Central US\" is not valid or not available for this account, did you mean \"centralus\"\\?$", 490 }, 491 { 492 name: "Non-premium instance disk type for compute", 493 edits: editFunctions{premiumDiskCompute, nonpremiumInstanceTypeDiskCompute}, 494 errorMsg: `compute\[0\].platform.azure.osDisk.diskType: Invalid value: "Premium_LRS": PremiumIO not supported for instance type Standard_D4_v4`, 495 }, 496 { 497 name: "Non-premium instance disk type for control-plane", 498 edits: editFunctions{premiumDiskControlPlane, nonpremiumInstanceTypeDiskControlPlane}, 499 errorMsg: `controlPlane.platform.azure.osDisk.diskType: Invalid value: "Premium_LRS": PremiumIO not supported for instance type Standard_D4_v4$`, 500 }, 501 { 502 name: "Supported AcceleratedNetworking as default", 503 edits: editFunctions{validVMNetworkingInstanceTypes, vmNetworkingTypeAcceleratedDefault}, 504 errorMsg: "", 505 }, 506 { 507 name: "Unsupported VMNetworkingType in Control Plane", 508 edits: editFunctions{invalidVMNetworkingIstanceTypes, vmNetworkingTypeAcceleratedControlPlane}, 509 errorMsg: `controlPlane.platform.azure.vmNetworkingType: Invalid value: "Accelerated": vm networking type is not supported for instance type Standard_B4ms`, 510 }, 511 { 512 name: "Unsupported VMNetworkingType in Compute", 513 edits: editFunctions{invalidVMNetworkingIstanceTypes, vmNetworkingTypeAcceleratedCompute}, 514 errorMsg: `compute\[0\].platform.azure.vmNetworkingType: Invalid value: "Accelerated": vm networking type is not supported for instance type Standard_B4ms`, 515 }, 516 { 517 name: "Supported ConfidentialVM security type", 518 edits: editFunctions{validConfidentialVMInstanceTypes, securityTypeConfidentialVMControlPlane}, 519 errorMsg: "", 520 }, 521 { 522 name: "Unsupported ConfidentialVM security type in control plane", 523 edits: editFunctions{invalidConfidentialVMInstanceTypes, securityTypeConfidentialVMControlPlane}, 524 errorMsg: `controlPlane.platform.azure.settings.securityType: Invalid value: "ConfidentialVM": this security type is not supported for instance type Standard_B4ms`, 525 }, 526 { 527 name: "Unsupported ConfidentialVM security type in control plane", 528 edits: editFunctions{invalidConfidentialVMSGXInstanceTypes, securityTypeConfidentialVMControlPlane}, 529 errorMsg: `controlPlane.platform.azure.settings.securityType: Invalid value: "ConfidentialVM": this security type is not supported for instance type Standard_DC8s_v3`, 530 }, 531 { 532 name: "Unsupported ConfidentialVM security type in compute", 533 edits: editFunctions{invalidConfidentialVMInstanceTypes, securityTypeConfidentialVMCompute}, 534 errorMsg: `compute\[0\].platform.azure.settings.securityType: Invalid value: "ConfidentialVM": this security type is not supported for instance type Standard_B4ms`, 535 }, 536 { 537 name: "Unsupported ConfidentialVM security type in compute for SGX instance type", 538 edits: editFunctions{invalidConfidentialVMSGXInstanceTypes, securityTypeConfidentialVMCompute}, 539 errorMsg: `compute\[0\].platform.azure.settings.securityType: Invalid value: "ConfidentialVM": this security type is not supported for instance type Standard_DC8s_v3`, 540 }, 541 { 542 name: "Unsupported ConfidentialVM security type in default machine platform", 543 edits: editFunctions{invalidConfidentialVMInstanceTypes, securityTypeConfidentialVMDefaultMachinePlatform}, 544 errorMsg: `[compute\[0\].platform.azure.settings.securityType: Invalid value: "ConfidentialVM": this security type is not supported for instance type Standard_B4ms,controlPlane.platform.azure.settings.securityType: Invalid valud: "ConfidentialVM": this security type is not supported for instance type Standard_B4ms]`, 545 }, 546 { 547 name: "Supported TrustedLaunch security type", 548 edits: editFunctions{validTrustedLaunchInstanceTypes, securityTypeTrustedLaunchControlPlane}, 549 errorMsg: "", 550 }, 551 { 552 name: "Unsupported TrustedLaunch security type in control plane", 553 edits: editFunctions{validArm64InstanceTypes, invalidTrustedLaunchInstanceTypes, securityTypeTrustedLaunchControlPlane}, 554 errorMsg: `controlPlane.platform.azure.settings.securityType: Invalid value: "TrustedLaunch": this security type is not supported for instance type Standard_D8ps_v5`, 555 }, 556 { 557 name: "Unsupported TrustedLaunch security type in compute", 558 edits: editFunctions{validArm64InstanceTypes, invalidTrustedLaunchInstanceTypes, securityTypeTrustedLaunchCompute}, 559 errorMsg: `compute\[0\].platform.azure.settings.securityType: Invalid value: "TrustedLaunch": this security type is not supported for instance type Standard_D4ps_v5`, 560 }, 561 { 562 name: "Unsupported TrustedLaunch security type for Confindential VM size in compute", 563 edits: editFunctions{validConfidentialVMInstanceTypes, securityTypeTrustedLaunchCompute}, 564 errorMsg: `compute\[0\].platform.azure.settings.securityType: Invalid value: "TrustedLaunch": this security type is not supported for instance type Standard_DC8ads_v5`, 565 }, 566 { 567 name: "Unsupported TrustedLaunch security type in default machine platform", 568 edits: editFunctions{validArm64InstanceTypes, invalidTrustedLaunchInstanceTypes, securityTypeTrustedLaunchDefaultMachinePlatform}, 569 errorMsg: `[compute\[0\].platform.azure.settings.securityType: Invalid value: "TrustedLaunch": this security type is not supported for instance type Standard_D4ps_v5,controlPlane.platform.azure.settings.securityType: Invalid valud: "ConfidentialVM": this security type is not supported for instance type Standard_D4ps_v5]`, 570 }, 571 } 572 573 mockCtrl := gomock.NewController(t) 574 defer mockCtrl.Finish() 575 576 azureClient := mock.NewMockAPI(mockCtrl) 577 578 // InstanceType 579 for _, value := range instanceTypeSku { 580 azureClient.EXPECT().GetVirtualMachineSku(gomock.Any(), to.String(value.Name), gomock.Any()).Return(value, nil).AnyTimes() 581 } 582 azureClient.EXPECT().GetVirtualMachineSku(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() 583 584 for key, value := range vmCapabilities { 585 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), key, validRegion).Return(value, nil).AnyTimes() 586 } 587 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), "Dne_D2_v4", validRegion).Return(nil, fmt.Errorf("not found in region centralus")).AnyTimes() 588 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), gomock.Any(), gomock.Any()).Return(vmCapabilities["Standard_D8s_v3"], nil).AnyTimes() 589 590 // VirtualNetwork 591 azureClient.EXPECT().GetVirtualNetwork(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork).Return(virtualNetworkAPIResult, nil).AnyTimes() 592 azureClient.EXPECT().GetVirtualNetwork(gomock.Any(), gomock.Not(validNetworkResourceGroup), gomock.Not(validVirtualNetwork)).Return(&aznetwork.VirtualNetwork{}, fmt.Errorf("invalid network resource group")).AnyTimes() 593 azureClient.EXPECT().GetVirtualNetwork(gomock.Any(), validNetworkResourceGroup, gomock.Not(validVirtualNetwork)).Return(&aznetwork.VirtualNetwork{}, fmt.Errorf("invalid virtual network")).AnyTimes() 594 595 // ComputeSubnet 596 azureClient.EXPECT().GetComputeSubnet(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork, validComputeSubnet).Return(computeSubnetAPIResult, nil).AnyTimes() 597 azureClient.EXPECT().GetComputeSubnet(gomock.Any(), gomock.Not(validNetworkResourceGroup), validVirtualNetwork, validComputeSubnet).Return(&aznetwork.Subnet{}, fmt.Errorf("invalid network resource group")).AnyTimes() 598 azureClient.EXPECT().GetComputeSubnet(gomock.Any(), validNetworkResourceGroup, gomock.Not(validVirtualNetwork), validComputeSubnet).Return(&aznetwork.Subnet{}, fmt.Errorf("invalid virtual network")).AnyTimes() 599 azureClient.EXPECT().GetComputeSubnet(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork, gomock.Not(validComputeSubnet)).Return(&aznetwork.Subnet{}, fmt.Errorf("invalid compute subnet")).AnyTimes() 600 601 // ControlPlaneSubnet 602 azureClient.EXPECT().GetControlPlaneSubnet(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork, validControlPlaneSubnet).Return(controlPlaneSubnetAPIResult, nil).AnyTimes() 603 azureClient.EXPECT().GetControlPlaneSubnet(gomock.Any(), gomock.Not(validNetworkResourceGroup), validVirtualNetwork, validControlPlaneSubnet).Return(&aznetwork.Subnet{}, fmt.Errorf("invalid network resource group")).AnyTimes() 604 azureClient.EXPECT().GetControlPlaneSubnet(gomock.Any(), validNetworkResourceGroup, gomock.Not(validVirtualNetwork), validControlPlaneSubnet).Return(&aznetwork.Subnet{}, fmt.Errorf("invalid virtual network")).AnyTimes() 605 azureClient.EXPECT().GetControlPlaneSubnet(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork, gomock.Not(validControlPlaneSubnet)).Return(&aznetwork.Subnet{}, fmt.Errorf("invalid control plane subnet")).AnyTimes() 606 607 // Location 608 azureClient.EXPECT().ListLocations(gomock.Any()).Return(locationsAPIResult, nil).AnyTimes() 609 610 // ResourceProvider 611 azureClient.EXPECT().GetResourcesProvider(gomock.Any(), validResourceGroupNamespace).Return(resourcesProviderAPIResult, nil).AnyTimes() 612 613 // Resource SKUs 614 azureClient.EXPECT().GetDiskSkus(gomock.Any(), validResourceSkuRegions).Return(nil, fmt.Errorf("invalid disk type")).AnyTimes() 615 azureClient.EXPECT().GetDiskSkus(gomock.Any(), invalidResourceSkuRegion).Return(nil, fmt.Errorf("invalid region")).AnyTimes() 616 617 azureClient.EXPECT().GetAvailabilityZones(gomock.Any(), gomock.Any(), gomock.Any()).Return([]string{"1", "2", "3"}, nil).AnyTimes() 618 619 azureClient.EXPECT().GetVirtualMachineFamily(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes() 620 621 for _, tc := range cases { 622 t.Run(tc.name, func(t *testing.T) { 623 editedInstallConfig := validInstallConfig() 624 for _, edit := range tc.edits { 625 edit(editedInstallConfig) 626 } 627 628 aggregatedErrors := Validate(azureClient, editedInstallConfig) 629 if tc.errorMsg != "" { 630 assert.Regexp(t, tc.errorMsg, aggregatedErrors) 631 } else { 632 assert.NoError(t, aggregatedErrors) 633 } 634 }) 635 } 636 } 637 638 var validGroupResult = &azres.Group{ 639 ID: to.StringPtr("valid-resource-group"), 640 Location: to.StringPtr("centralus"), 641 } 642 643 var invalidGroupOutsideRegionResult = &azres.Group{ 644 ID: to.StringPtr("invalid-resource-group-useast2"), 645 Location: to.StringPtr("useast2"), 646 } 647 648 var validGroupWithTagsResult = &azres.Group{ 649 ID: to.StringPtr("valid-resource-group-tags"), 650 Location: to.StringPtr("centralus"), 651 Tags: map[string]*string{ 652 "key": to.StringPtr("value"), 653 }, 654 } 655 656 var validGroupWithConflictinsTagsResult = &azres.Group{ 657 ID: to.StringPtr("valid-resource-group-conf-tags"), 658 Location: to.StringPtr("centralus"), 659 Tags: map[string]*string{ 660 "kubernetes.io_cluster.test-cluster-12345": to.StringPtr("owned"), 661 }, 662 } 663 664 func Test_validateResourceGroup(t *testing.T) { 665 cases := []struct { 666 groupName string 667 wantSkip bool 668 err string 669 }{{ 670 groupName: "non-existent-group", 671 err: `^\Qplatform.azure.resourceGroupName: Internal error: failed to get resource group: resource group /resourceGroups/non-existent-group was not found\E$`, 672 }, { 673 groupName: "valid-resource-group", 674 }, { 675 groupName: "invalid-resource-group-useast2", 676 err: `^\Qplatform.azure.resourceGroupName: Invalid value: "invalid-resource-group-useast2": expected to in region centralus, but found it to be in useast2\E$`, 677 }, { 678 groupName: "valid-resource-group-tags", 679 }, { 680 groupName: "valid-resource-group-conf-tags", 681 err: `^\Qplatform.azure.resourceGroupName: Invalid value: "valid-resource-group-conf-tags": resource group has conflicting tags kubernetes.io_cluster.test-cluster-12345\E$`, 682 }, { 683 groupName: "valid-resource-group-with-resources", 684 // ARO provisions Azure resources before resolving the asset graph, 685 // so there will always be resources in its resource group. 686 wantSkip: (&azure.Platform{}).IsARO(), 687 err: `^\Qplatform.azure.resourceGroupName: Invalid value: "valid-resource-group-with-resources": resource group must be empty but it has 3 resources like id1, id2 ...\E$`, 688 }} 689 690 mockCtrl := gomock.NewController(t) 691 defer mockCtrl.Finish() 692 693 azureClient := mock.NewMockAPI(mockCtrl) 694 azureClient.EXPECT().GetGroup(gomock.Any(), "non-existent-group").Return(nil, fmt.Errorf("resource group /resourceGroups/non-existent-group was not found")).AnyTimes() 695 azureClient.EXPECT().GetGroup(gomock.Any(), "valid-resource-group").Return(validGroupResult, nil).AnyTimes() 696 azureClient.EXPECT().GetGroup(gomock.Any(), "invalid-resource-group-useast2").Return(invalidGroupOutsideRegionResult, nil).AnyTimes() 697 azureClient.EXPECT().GetGroup(gomock.Any(), "valid-resource-group-with-resources").Return(validGroupResult, nil).AnyTimes() 698 azureClient.EXPECT().GetGroup(gomock.Any(), "valid-resource-group-tags").Return(validGroupWithTagsResult, nil).AnyTimes() 699 azureClient.EXPECT().GetGroup(gomock.Any(), "valid-resource-group-conf-tags").Return(validGroupWithConflictinsTagsResult, nil).AnyTimes() 700 azureClient.EXPECT().ListResourceIDsByGroup(gomock.Any(), gomock.Not("valid-resource-group-with-resources")).Return(nil, nil).AnyTimes() 701 azureClient.EXPECT().ListResourceIDsByGroup(gomock.Any(), "valid-resource-group-with-resources").Return([]string{"id1", "id2", "id3"}, nil).AnyTimes() 702 703 for _, test := range cases { 704 t.Run("", func(t *testing.T) { 705 if test.wantSkip { 706 t.Skip() 707 } 708 err := validateResourceGroup(azureClient, field.NewPath("platform").Child("azure"), &azure.Platform{ResourceGroupName: test.groupName, Region: "centralus"}) 709 if test.err != "" { 710 assert.Regexp(t, test.err, err.ToAggregate()) 711 } else { 712 assert.NoError(t, err.ToAggregate()) 713 } 714 }) 715 } 716 } 717 718 func TestCheckAzureStackClusterOSImageSet(t *testing.T) { 719 cases := []struct { 720 ClusterOSImage string 721 err string 722 }{{ 723 ClusterOSImage: "https://storage.test-endpoint.com/rhcos-image", 724 err: "", 725 }, { 726 ClusterOSImage: "", 727 err: "^platform.azure.clusterOSImage: Required value: clusterOSImage must be set when installing on Azure Stack$", 728 }} 729 for _, test := range cases { 730 t.Run("", func(t *testing.T) { 731 err := checkAzureStackClusterOSImageSet(test.ClusterOSImage, field.NewPath("platform").Child("azure")) 732 if test.err != "" { 733 assert.Regexp(t, test.err, err.ToAggregate()) 734 } else { 735 assert.NoError(t, err.ToAggregate()) 736 } 737 }) 738 } 739 } 740 741 func TestValidateAzureStackClusterOSImage(t *testing.T) { 742 cases := []struct { 743 StorageEndpointSuffix string 744 ClusterOSImage string 745 err string 746 }{{ 747 StorageEndpointSuffix: "storage.test-endpoint.com", 748 ClusterOSImage: "https://storage.test-endpoint.com/rhcos-image", 749 err: "", 750 }, { 751 StorageEndpointSuffix: "storage.test-endpoint.com", 752 ClusterOSImage: "https://storage.not-in-the-cluster.com/rhcos-image", 753 err: `^platform.azure.clusterOSImage: Invalid value: "https://storage.not-in-the-cluster.com/rhcos-image": clusterOSImage must be in the Azure Stack environment$`, 754 }} 755 for _, test := range cases { 756 t.Run("", func(t *testing.T) { 757 err := validateAzureStackClusterOSImage(test.StorageEndpointSuffix, test.ClusterOSImage, field.NewPath("platform").Child("azure")) 758 if test.err != "" { 759 assert.Regexp(t, test.err, err.ToAggregate()) 760 } else { 761 assert.NoError(t, err.ToAggregate()) 762 } 763 }) 764 } 765 } 766 767 func TestAzureDiskEncryptionSet(t *testing.T) { 768 cases := []struct { 769 name string 770 edits editFunctions 771 errorMsg string 772 }{ 773 { 774 name: "Valid disk encryption set for default pool", 775 edits: editFunctions{validDiskEncryptionSetDefaultMachinePlatform}, 776 errorMsg: "", 777 }, 778 { 779 name: "Invalid disk encryption set for default pool", 780 edits: editFunctions{invalidDiskEncryptionSetDefaultMachinePlatform}, 781 errorMsg: fmt.Sprintf(`^platform.azure.defaultMachinePlatform.osDisk.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: failed to get disk encryption set$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName), 782 }, 783 { 784 name: "Valid disk encryption set for control-plane", 785 edits: editFunctions{validDiskEncryptionSetControlPlane}, 786 errorMsg: "", 787 }, 788 { 789 name: "Invalid disk encryption set for control-plane", 790 edits: editFunctions{invalidDiskEncryptionSetControlPlane}, 791 errorMsg: fmt.Sprintf(`^platform.azure.osDisk.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: failed to get disk encryption set$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName), 792 }, 793 { 794 name: "Valid disk encryption set for compute", 795 edits: editFunctions{validDiskEncryptionSetCompute}, 796 errorMsg: "", 797 }, 798 { 799 name: "Invalid disk encryption set for compute", 800 edits: editFunctions{invalidDiskEncryptionSetCompute}, 801 errorMsg: fmt.Sprintf(`^compute\[0\].platform.azure.osDisk.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: failed to get disk encryption set$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName), 802 }, 803 } 804 805 mockCtrl := gomock.NewController(t) 806 defer mockCtrl.Finish() 807 808 azureClient := mock.NewMockAPI(mockCtrl) 809 810 // DiskEncryptionSet 811 azureClient.EXPECT().GetDiskEncryptionSet(gomock.Any(), validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, validDiskEncryptionSetName).Return(validDiskEncryptionSetResult, nil).AnyTimes() 812 azureClient.EXPECT().GetDiskEncryptionSet(gomock.Any(), validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName).Return(nil, fmt.Errorf("failed to get disk encryption set")).AnyTimes() 813 814 for _, tc := range cases { 815 t.Run(tc.name, func(t *testing.T) { 816 editedInstallConfig := validInstallConfig() 817 for _, edit := range tc.edits { 818 edit(editedInstallConfig) 819 } 820 821 errors := ValidateDiskEncryptionSet(azureClient, editedInstallConfig) 822 aggregatedErrors := errors.ToAggregate() 823 if tc.errorMsg != "" { 824 assert.Regexp(t, tc.errorMsg, aggregatedErrors) 825 } else { 826 assert.NoError(t, aggregatedErrors) 827 } 828 }) 829 } 830 } 831 832 func TestAzureSecurityProfileDiskEncryptionSet(t *testing.T) { 833 cases := []struct { 834 name string 835 edits editFunctions 836 errorMsg string 837 }{ 838 { 839 name: "Valid security profile disk encryption set for default pool", 840 edits: editFunctions{validConfidentialVMDiskEncryptionSetDefaultMachinePlatform}, 841 errorMsg: "", 842 }, 843 { 844 name: "Invalid security profile disk encryption set not found for default pool", 845 edits: editFunctions{invalidConfidentialVMDiskEncryptionSetDefaultMachinePlatform}, 846 errorMsg: fmt.Sprintf(`^platform.azure.defaultMachinePlatform.osDisk.securityProfile.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: failed to get disk encryption set$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName), 847 }, 848 { 849 name: "Invalid security profile disk encryption set with default encryption type for default pool", 850 edits: editFunctions{invalidTypeConfidentialVMDiskEncryptionSetDefaultMachinePlatform}, 851 errorMsg: fmt.Sprintf(`^platform.azure.defaultMachinePlatform.osDisk.securityProfile.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: the disk encryption set should be created with type %s$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, validDiskEncryptionSetName, azenc.ConfidentialVMEncryptedWithCustomerKey), 852 }, 853 { 854 name: "Valid security profile disk encryption set for control-plane", 855 edits: editFunctions{validConfidentialVMDiskEncryptionSetControlPlane}, 856 errorMsg: "", 857 }, 858 { 859 name: "Invalid security profile disk encryption set not found for control-plane", 860 edits: editFunctions{invalidConfidentialVMDiskEncryptionSetControlPlane}, 861 errorMsg: fmt.Sprintf(`^platform.azure.osDisk.securityProfile.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: failed to get disk encryption set$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName), 862 }, 863 { 864 name: "Invalid security profile disk encryption set with default encryption type for control-plane", 865 edits: editFunctions{invalidTypeConfidentialVMDiskEncryptionSetControlPlane}, 866 errorMsg: fmt.Sprintf(`^platform.azure.osDisk.securityProfile.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: the disk encryption set should be created with type %s$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, validDiskEncryptionSetName, azenc.ConfidentialVMEncryptedWithCustomerKey), 867 }, 868 { 869 name: "Valid security profile disk encryption set for compute", 870 edits: editFunctions{validConfidentialVMDiskEncryptionSetCompute}, 871 errorMsg: "", 872 }, 873 { 874 name: "Invalid security profile disk encryption set not found for compute", 875 edits: editFunctions{invalidConfidentialVMDiskEncryptionSetCompute}, 876 errorMsg: fmt.Sprintf(`^compute\[0\].platform.azure.osDisk.securityProfile.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: failed to get disk encryption set$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName), 877 }, 878 { 879 name: "Invalid security profile disk encryption set with default encryption type for compute", 880 edits: editFunctions{invalidTypeConfidentialVMDiskEncryptionSetCompute}, 881 errorMsg: fmt.Sprintf(`^compute\[0\].platform.azure.osDisk.securityProfile.diskEncryptionSet: Invalid value: azure.DiskEncryptionSet{SubscriptionID:"%s", ResourceGroup:"%s", Name:"%s"}: the disk encryption set should be created with type %s$`, validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, validDiskEncryptionSetName, azenc.ConfidentialVMEncryptedWithCustomerKey), 882 }, 883 } 884 885 mockCtrl := gomock.NewController(t) 886 defer mockCtrl.Finish() 887 888 azureClient := mock.NewMockAPI(mockCtrl) 889 890 azureClient.EXPECT().GetDiskEncryptionSet(gomock.Any(), validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, validConfidentialVMDiskEncryptionSetName).Return(validConfidentialVMDiskEncryptionSetResult, nil).AnyTimes() 891 azureClient.EXPECT().GetDiskEncryptionSet(gomock.Any(), validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, validDiskEncryptionSetName).Return(validDiskEncryptionSetResult, nil).AnyTimes() 892 azureClient.EXPECT().GetDiskEncryptionSet(gomock.Any(), validDiskEncryptionSetSubscriptionID, validDiskEncryptionSetResourceGroup, invalidDiskEncryptionSetName).Return(nil, fmt.Errorf("failed to get disk encryption set")).AnyTimes() 893 894 for _, tc := range cases { 895 t.Run(tc.name, func(t *testing.T) { 896 editedInstallConfig := validInstallConfig() 897 for _, edit := range tc.edits { 898 edit(editedInstallConfig) 899 } 900 901 errors := ValidateSecurityProfileDiskEncryptionSet(azureClient, editedInstallConfig) 902 aggregatedErrors := errors.ToAggregate() 903 if tc.errorMsg != "" { 904 assert.Regexp(t, tc.errorMsg, aggregatedErrors) 905 } else { 906 assert.NoError(t, aggregatedErrors) 907 } 908 }) 909 } 910 } 911 912 func TestAzureUltraSSDCapability(t *testing.T) { 913 locationInfoFull := &azenc.ResourceSkuLocationInfo{ 914 Location: to.StringPtr("centralus"), 915 ZoneDetails: &[]azenc.ResourceSkuZoneDetails{ 916 { 917 Name: to.StringSlicePtr([]string{"1", "3", "2"}), 918 Capabilities: &[]azenc.ResourceSkuCapabilities{ 919 {Name: to.StringPtr("UltraSSDAvailable"), Value: to.StringPtr("True")}, 920 }, 921 }, 922 }, 923 Zones: to.StringSlicePtr([]string{"1", "2", "3"}), 924 } 925 locationInfoNoSSD := &azenc.ResourceSkuLocationInfo{ 926 Location: to.StringPtr("centralus"), 927 ZoneDetails: &[]azenc.ResourceSkuZoneDetails{ 928 { 929 Name: to.StringSlicePtr([]string{"1", "3", "2"}), 930 Capabilities: &[]azenc.ResourceSkuCapabilities{}, 931 }, 932 }, 933 Zones: to.StringSlicePtr([]string{"1", "2", "3"}), 934 } 935 locationInfoPartial := &azenc.ResourceSkuLocationInfo{ 936 Location: to.StringPtr("francecentral"), 937 ZoneDetails: &[]azenc.ResourceSkuZoneDetails{ 938 { 939 Name: to.StringSlicePtr([]string{"2", "3"}), 940 Capabilities: &[]azenc.ResourceSkuCapabilities{ 941 {Name: to.StringPtr("UltraSSDAvailable"), Value: to.StringPtr("True")}, 942 }, 943 }, 944 }, 945 Zones: to.StringSlicePtr([]string{"1", "2", "3"}), 946 } 947 locationInfoSingle := &azenc.ResourceSkuLocationInfo{ 948 Location: to.StringPtr("northcentralus"), 949 ZoneDetails: &[]azenc.ResourceSkuZoneDetails{}, 950 Zones: to.StringSlicePtr(nil), 951 } 952 locationInfoEmpty := &azenc.ResourceSkuLocationInfo{ 953 Location: to.StringPtr("azurestack"), 954 ZoneDetails: nil, 955 Zones: to.StringSlicePtr(nil), 956 } 957 958 ultraSSDSupportedInstanceTypes := func(ic *types.InstallConfig) { 959 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D8s_v3" 960 } 961 ultraSSDUnsupportedInstanceTypes := func(ic *types.InstallConfig) { 962 ic.Platform.Azure.DefaultMachinePlatform.InstanceType = "Standard_D2s_v3" 963 } 964 noZoneRegion := func(ic *types.InstallConfig) { 965 ic.Platform.Azure.Region = "azurestack" 966 } 967 singleZoneRegion := func(ic *types.InstallConfig) { 968 ic.Platform.Azure.Region = "northcentralus" 969 } 970 twoZoneRegion := func(ic *types.InstallConfig) { 971 ic.Platform.Azure.Region = "francecentral" 972 } 973 974 // User provided availability zones restrictions 975 setZones := func(where string, zones ...string) func(ic *types.InstallConfig) { 976 switch where { 977 case "controlplane", "master": 978 return func(ic *types.InstallConfig) { 979 ic.ControlPlane.Platform.Azure.Zones = zones 980 } 981 case "compute", "worker": 982 return func(ic *types.InstallConfig) { 983 ic.Compute[0].Platform.Azure.Zones = zones 984 } 985 default: 986 return func(ic *types.InstallConfig) { 987 ic.Platform.Azure.DefaultMachinePlatform.Zones = zones 988 } 989 } 990 } 991 992 cases := []struct { 993 name string 994 edits editFunctions 995 errorMsg string 996 }{ 997 // Tests that should fail 998 { 999 name: "Unsupported LocationInfo", 1000 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, invalidateRegion}, 1001 errorMsg: `\[platform.azure.region: Invalid value: "neverland": region "neverland" is not valid or not available for this account, controlPlane.platform.azure.type: Invalid value: "Standard_D8s_v3": could not determine Availability Zones support in the neverland region: error retrieving availability zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D8s_v3": could not determine Availability Zones support in the neverland region: error retrieving availability zones\]$`, 1002 }, 1003 { 1004 name: "Unsupported UltraSSD in No Zone region when set in DefaultMachine", 1005 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDUnsupportedInstanceTypes, noZoneRegion}, 1006 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones\]$`, 1007 }, 1008 { 1009 name: "Unsupported UltraSSD in No Zone region when set in ControlPlane", 1010 edits: editFunctions{enabledSSDCapabilityControlPlane, ultraSSDUnsupportedInstanceTypes, noZoneRegion}, 1011 errorMsg: `controlPlane.platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones$`, 1012 }, 1013 { 1014 name: "Unsupported UltraSSD in No Zone region when set in Compute", 1015 edits: editFunctions{enabledSSDCapabilityCompute, ultraSSDUnsupportedInstanceTypes, noZoneRegion}, 1016 errorMsg: `compute\[0\].platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones$`, 1017 }, 1018 { 1019 name: "Unsupported UltraSSD in No Zone region when set in ControlPlane and Compute", 1020 edits: editFunctions{enabledSSDCapabilityControlPlane, enabledSSDCapabilityCompute, ultraSSDUnsupportedInstanceTypes, noZoneRegion}, 1021 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones\]$`, 1022 }, 1023 { 1024 name: "Unsupported UltraSSD in No Zone region when set in ControlPlane and DefaultMachine", 1025 edits: editFunctions{enabledSSDCapabilityDefault, enabledSSDCapabilityControlPlane, ultraSSDUnsupportedInstanceTypes, noZoneRegion}, 1026 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones\]$`, 1027 }, 1028 { 1029 name: "Unsupported UltraSSD in Single Zone region when set in DefaultMachine", 1030 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDUnsupportedInstanceTypes, singleZoneRegion}, 1031 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region northcentralus does not support Availability Zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region northcentralus does not support Availability Zones\]`, 1032 }, 1033 { 1034 name: "Unsupported UltraSSD in Single Zone region when set in ControlPlane", 1035 edits: editFunctions{enabledSSDCapabilityControlPlane, ultraSSDUnsupportedInstanceTypes, singleZoneRegion}, 1036 errorMsg: `controlPlane.platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region northcentralus does not support Availability Zones$`, 1037 }, 1038 { 1039 name: "Unsupported UltraSSD in Single Zone region when set in Compute", 1040 edits: editFunctions{enabledSSDCapabilityCompute, ultraSSDUnsupportedInstanceTypes, singleZoneRegion}, 1041 errorMsg: `compute\[0\].platform.azure.type: Invalid value: "Standard_D2s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region northcentralus does not support Availability Zones$`, 1042 }, 1043 { 1044 name: "Unsupported UltraSSD in No Zone region because of Availability Sets", 1045 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, noZoneRegion}, 1046 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region azurestack does not support Availability Zones\]$`, 1047 }, 1048 { 1049 name: "Unsupported UltraSSD in Single Zone region because of Availability Sets", 1050 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, singleZoneRegion}, 1051 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region northcentralus does not support Availability Zones, compute\[0\].platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability is not compatible with Availability Sets which are used because region northcentralus does not support Availability Zones\]$`, 1052 }, 1053 { 1054 name: "Unsupported UltraSSD in Two Zone region when set in DefaultMachine and zones not specified", 1055 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, twoZoneRegion}, 1056 errorMsg: `compute\[0\].platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability only supported in zones \[2 3\] for this instance type in the francecentral region`, 1057 }, 1058 { 1059 name: "Unsupported UltraSSD in Two Zone region when set in DefaultMachine and single wrong zone specified", 1060 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("default", "1")}, 1061 errorMsg: `controlPlane.platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability only supported in zones \[2 3\] for this instance type in the francecentral region`, 1062 }, 1063 { 1064 name: "Unsupported UltraSSD in Two Zone region when set in DefaultMachine and one wrong zone specified", 1065 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("default", "1", "2")}, 1066 errorMsg: `\[controlPlane.platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability only supported in zones \[2 3\] for this instance type in the francecentral region, compute\[0\].platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability only supported in zones \[2 3\] for this instance type in the francecentral region\]`, 1067 }, 1068 { 1069 name: "Unsupported UltraSSD in Two Zone region when set in DefaultMachine and one wrong zone specified for ControlPlane", 1070 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("master", "1", "2")}, 1071 errorMsg: `controlPlane.platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability only supported in zones \[2 3\] for this instance type in the francecentral region`, 1072 }, 1073 { 1074 name: "Unsupported UltraSSD in Two Zone region when set in DefaultMachine and one wrong zone specified for Compute", 1075 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("worker", "1", "2")}, 1076 errorMsg: `compute\[0\].platform.azure.type: Invalid value: "Standard_D8s_v3": UltraSSD capability only supported in zones \[2 3\] for this instance type in the francecentral region`, 1077 }, 1078 // Tests that should succeed 1079 { 1080 name: "Unsupported UltraSSD in No Zone region when not set in config", 1081 edits: editFunctions{ultraSSDUnsupportedInstanceTypes, noZoneRegion}, 1082 errorMsg: "", 1083 }, 1084 { 1085 name: "Unsupported UltraSSD in Single Zone region when not set in config", 1086 edits: editFunctions{ultraSSDUnsupportedInstanceTypes, singleZoneRegion}, 1087 errorMsg: "", 1088 }, 1089 { 1090 name: "Unsupported UltraSSD in Multi Zone region when not set in config", 1091 edits: editFunctions{ultraSSDUnsupportedInstanceTypes}, 1092 errorMsg: "", 1093 }, 1094 { 1095 name: "Supported UltraSSD in Two Zone region when set in DefaultMachine and correct zones specified", 1096 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("default", "2", "3")}, 1097 errorMsg: "", 1098 }, 1099 { 1100 name: "Supported UltraSSD in Two Zone region when set in ControlPlane and one wrong zone specified for DefaultMachine but correct zones for ControlPlane", 1101 edits: editFunctions{enabledSSDCapabilityControlPlane, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("default", "1", "2"), setZones("master", "2", "3")}, 1102 errorMsg: "", 1103 }, 1104 { 1105 name: "Supported UltraSSD in Two Zone region when set in Compute and one wrong zone specified for DefaultMachine but correct zones for Compute", 1106 edits: editFunctions{enabledSSDCapabilityCompute, ultraSSDSupportedInstanceTypes, twoZoneRegion, setZones("default", "1", "2"), setZones("worker", "2", "3")}, 1107 errorMsg: "", 1108 }, 1109 { 1110 name: "Supported UltraSSD in Multi Zone region when set in DefaultMachine and no zones specified", 1111 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes}, 1112 errorMsg: "", 1113 }, 1114 { 1115 name: "Supported UltraSSD in Multi Zone region when set in DefaultMachine and single zone specified", 1116 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, setZones("default", "1")}, 1117 errorMsg: "", 1118 }, 1119 { 1120 name: "Supported UltraSSD in Multi Zone region when set in DefaultMachine and two zones specified", 1121 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, setZones("default", "1", "2")}, 1122 errorMsg: "", 1123 }, 1124 { 1125 name: "Supported UltraSSD in MultiZone region when set in DefaultMachine and all zones specified", 1126 edits: editFunctions{enabledSSDCapabilityDefault, ultraSSDSupportedInstanceTypes, setZones("default", "1", "2", "3")}, 1127 errorMsg: "", 1128 }, 1129 } 1130 1131 mockCtrl := gomock.NewController(t) 1132 defer mockCtrl.Finish() 1133 1134 azureClient := mock.NewMockAPI(mockCtrl) 1135 1136 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), "Standard_D8s_v3", gomock.Any()).Return(vmCapabilities["Standard_D8s_v3"], nil).AnyTimes() 1137 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), "Standard_D2s_v3", gomock.Any()).Return(vmCapabilities["Standard_D2s_v3"], nil).AnyTimes() 1138 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() 1139 1140 azureClient.EXPECT().GetLocationInfo(gomock.Any(), "centralus", "Standard_D8s_v3").Return(locationInfoFull, nil).AnyTimes() 1141 azureClient.EXPECT().GetLocationInfo(gomock.Any(), "centralus", "Standard_D2s_v3").Return(locationInfoNoSSD, nil).AnyTimes() 1142 azureClient.EXPECT().GetLocationInfo(gomock.Any(), "francecentral", "Standard_D8s_v3").Return(locationInfoPartial, nil).AnyTimes() 1143 azureClient.EXPECT().GetLocationInfo(gomock.Any(), "francecentral", "Standard_D2s_v3").Return(locationInfoNoSSD, nil).AnyTimes() 1144 azureClient.EXPECT().GetLocationInfo(gomock.Any(), "northcentralus", gomock.Any()).Return(locationInfoSingle, nil).AnyTimes() 1145 azureClient.EXPECT().GetLocationInfo(gomock.Any(), "azurestack", gomock.Any()).Return(locationInfoEmpty, nil).AnyTimes() 1146 azureClient.EXPECT().GetLocationInfo(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error retrieving availability zones")).AnyTimes() 1147 1148 // VirtualNetwork 1149 azureClient.EXPECT().GetVirtualNetwork(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork).Return(virtualNetworkAPIResult, nil).AnyTimes() 1150 // ComputeSubnet 1151 azureClient.EXPECT().GetComputeSubnet(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork, validComputeSubnet).Return(computeSubnetAPIResult, nil).AnyTimes() 1152 // ControlPlaneSubnet 1153 azureClient.EXPECT().GetControlPlaneSubnet(gomock.Any(), validNetworkResourceGroup, validVirtualNetwork, validControlPlaneSubnet).Return(controlPlaneSubnetAPIResult, nil).AnyTimes() 1154 1155 validRegionList := []string{"centralus", "northcentralus", "francecentral", "azurestack"} 1156 locationsAPIResult = func() *[]azsubs.Location { 1157 r := []azsubs.Location{} 1158 for i := 0; i < len(validRegionList); i++ { 1159 r = append(r, azsubs.Location{Name: to.StringPtr(validRegionList[i]), DisplayName: to.StringPtr(validRegionList[i])}) 1160 } 1161 return &r 1162 }() 1163 // Location 1164 azureClient.EXPECT().ListLocations(gomock.Any()).Return(locationsAPIResult, nil).AnyTimes() 1165 1166 resourcesProviderAPIResult = &azres.Provider{ 1167 Namespace: to.StringPtr(validResourceGroupNamespace), 1168 ResourceTypes: &[]azres.ProviderResourceType{ 1169 { 1170 ResourceType: &validResourceGroupResourceType, 1171 Locations: &validRegionList, 1172 }, 1173 }, 1174 } 1175 // ResourceProvider 1176 azureClient.EXPECT().GetResourcesProvider(gomock.Any(), validResourceGroupNamespace).Return(resourcesProviderAPIResult, nil).AnyTimes() 1177 1178 azureClient.EXPECT().GetVirtualMachineFamily(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes() 1179 1180 for _, tc := range cases { 1181 t.Run(tc.name, func(t *testing.T) { 1182 editedInstallConfig := validInstallConfig() 1183 for _, edit := range tc.edits { 1184 edit(editedInstallConfig) 1185 } 1186 1187 aggregatedErrors := Validate(azureClient, editedInstallConfig) 1188 if tc.errorMsg != "" { 1189 assert.Regexp(t, tc.errorMsg, aggregatedErrors) 1190 } else { 1191 assert.NoError(t, aggregatedErrors) 1192 } 1193 }) 1194 } 1195 } 1196 1197 func TestAzureMarketplaceImage(t *testing.T) { 1198 validOSImageNoPlan := azure.OSImage{ 1199 Plan: azure.ImageNoPurchasePlan, 1200 Publisher: validOSImagePublisher, 1201 SKU: noPlanOSImageSKU, 1202 Version: validOSImageVersion, 1203 Offer: validOSImageOffer, 1204 } 1205 1206 invalidOSImage := azure.OSImage{ 1207 Publisher: validOSImagePublisher, 1208 SKU: invalidOSImageSKU, 1209 Version: validOSImageVersion, 1210 Offer: validOSImageOffer, 1211 } 1212 1213 allHyperVGens := sets.New("V1", "V2") 1214 1215 cases := []struct { 1216 name string 1217 osImage *azure.OSImage 1218 hyperVGens sets.Set[string] 1219 errorMsg string 1220 }{ 1221 { 1222 name: "Valid OS Image", 1223 osImage: &validOSImage, 1224 hyperVGens: allHyperVGens, 1225 errorMsg: "", 1226 }, 1227 { 1228 name: "Valid OS Image no purchase plan", 1229 osImage: &validOSImageNoPlan, 1230 hyperVGens: allHyperVGens, 1231 errorMsg: "", 1232 }, 1233 { 1234 name: "Invalid OS Image", 1235 osImage: &invalidOSImage, 1236 hyperVGens: allHyperVGens, 1237 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: not found`, 1238 }, 1239 { 1240 name: "OS Image causing error determining license terms", 1241 osImage: &azure.OSImage{ 1242 Publisher: validOSImagePublisher, 1243 SKU: erroringLicenseTermsOSImageSKU, 1244 Offer: validOSImageOffer, 1245 Version: validOSImageVersion, 1246 }, 1247 hyperVGens: allHyperVGens, 1248 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: could not determine if the license terms for the marketplace image have been accepted: error`, 1249 }, 1250 { 1251 name: "OS Image with unaccepted license terms", 1252 osImage: &azure.OSImage{ 1253 Publisher: validOSImagePublisher, 1254 SKU: unacceptedLicenseTermsOSImageSKU, 1255 Offer: validOSImageOffer, 1256 Version: validOSImageVersion, 1257 }, 1258 hyperVGens: allHyperVGens, 1259 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: the license terms for the marketplace image have not been accepted`, 1260 }, 1261 { 1262 name: "OS Image with wrong HyperV generation", 1263 osImage: &azure.OSImage{ 1264 Publisher: validOSImagePublisher, 1265 SKU: erroringOSImageSKU, 1266 Offer: validOSImageOffer, 1267 Version: validOSImageVersion, 1268 }, 1269 hyperVGens: sets.New("V1"), 1270 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*$`, 1271 }, 1272 } 1273 1274 mockCtrl := gomock.NewController(t) 1275 defer mockCtrl.Finish() 1276 1277 azureClient := mock.NewMockAPI(mockCtrl) 1278 1279 // OS Images 1280 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, validOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, nil).AnyTimes() 1281 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, validOSImageSKU).Return(true, nil).AnyTimes() 1282 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, invalidOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, fmt.Errorf("not found")).AnyTimes() 1283 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, erroringLicenseTermsOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, nil).AnyTimes() 1284 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, erroringLicenseTermsOSImageSKU).Return(false, fmt.Errorf("error")).AnyTimes() 1285 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, unacceptedLicenseTermsOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, nil).AnyTimes() 1286 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, unacceptedLicenseTermsOSImageSKU).Return(false, nil).AnyTimes() 1287 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, erroringOSImageSKU, validOSImageVersion).Return(azenc.VirtualMachineImage{ 1288 VirtualMachineImageProperties: &azenc.VirtualMachineImageProperties{ 1289 HyperVGeneration: azenc.HyperVGenerationTypesV2, 1290 }, 1291 }, nil).AnyTimes() 1292 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, erroringOSImageSKU).Return(true, nil).AnyTimes() 1293 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, noPlanOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResultNoPlan, nil).AnyTimes() 1294 // Should not check terms of images with no purchase plan 1295 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, noPlanOSImageSKU).MaxTimes(0) 1296 1297 for _, tc := range cases { 1298 t.Run(tc.name, func(t *testing.T) { 1299 err := validateMarketplaceImage(azureClient, validRegion, tc.hyperVGens, tc.osImage, field.NewPath("compute").Index(0)) 1300 if tc.errorMsg != "" { 1301 assert.Regexp(t, tc.errorMsg, err) 1302 } else { 1303 assert.Nil(t, err) 1304 } 1305 }) 1306 } 1307 } 1308 1309 func TestAzureMarketplaceImages(t *testing.T) { 1310 defaultMachineInstanceTypeHyperVGen1 := func(ic *types.InstallConfig) { 1311 ic.Azure.DefaultMachinePlatform.InstanceType = "Standard_D4s_v3" 1312 } 1313 1314 validOSImageDefaultMachine := func(ic *types.InstallConfig) { 1315 ic.Azure.DefaultMachinePlatform.OSImage = validOSImage 1316 } 1317 invalidOSImageDefaultMachine := func(ic *types.InstallConfig) { 1318 validOSImageDefaultMachine(ic) 1319 ic.Azure.DefaultMachinePlatform.OSImage.SKU = invalidOSImageSKU 1320 } 1321 erroringLicenseTermsOSImageDefaultMachine := func(ic *types.InstallConfig) { 1322 validOSImageDefaultMachine(ic) 1323 ic.Azure.DefaultMachinePlatform.OSImage.SKU = erroringLicenseTermsOSImageSKU 1324 } 1325 unacceptedLicenseTermsOSImageDefaultMachine := func(ic *types.InstallConfig) { 1326 validOSImageDefaultMachine(ic) 1327 ic.Azure.DefaultMachinePlatform.OSImage.SKU = unacceptedLicenseTermsOSImageSKU 1328 } 1329 erroringGenerationOsImageDefaultMachine := func(ic *types.InstallConfig) { 1330 validOSImageDefaultMachine(ic) 1331 ic.Azure.DefaultMachinePlatform.OSImage.SKU = erroringOSImageSKU 1332 } 1333 1334 controlPlaneInstanceTypeHyperVGen1 := func(ic *types.InstallConfig) { 1335 ic.ControlPlane.Platform.Azure.InstanceType = "Standard_D4s_v3" 1336 } 1337 1338 validOSImageControlPlane := func(ic *types.InstallConfig) { 1339 ic.ControlPlane.Platform.Azure.OSImage = validOSImage 1340 } 1341 invalidOSImageControlPlane := func(ic *types.InstallConfig) { 1342 validOSImageControlPlane(ic) 1343 ic.ControlPlane.Platform.Azure.OSImage.SKU = invalidOSImageSKU 1344 } 1345 erroringLicenseTermsOSImageControlPlane := func(ic *types.InstallConfig) { 1346 validOSImageControlPlane(ic) 1347 ic.ControlPlane.Platform.Azure.OSImage.SKU = erroringLicenseTermsOSImageSKU 1348 } 1349 unacceptedLicenseTermsOSImageControlPlane := func(ic *types.InstallConfig) { 1350 validOSImageControlPlane(ic) 1351 ic.ControlPlane.Platform.Azure.OSImage.SKU = unacceptedLicenseTermsOSImageSKU 1352 } 1353 erroringGenerationOsImageControlPlane := func(ic *types.InstallConfig) { 1354 validOSImageControlPlane(ic) 1355 ic.ControlPlane.Platform.Azure.OSImage.SKU = erroringOSImageSKU 1356 } 1357 1358 cases := []struct { 1359 name string 1360 edits editFunctions 1361 errorMsg string 1362 }{ 1363 // Marketplace definition on compute nodes 1364 { 1365 name: "Valid OS Image compute", 1366 edits: editFunctions{validOSImageCompute, validInstanceTypes}, 1367 }, 1368 { 1369 name: "Invalid OS Image compute", 1370 edits: editFunctions{invalidOSImageCompute, validInstanceTypes}, 1371 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: not found`, 1372 }, 1373 { 1374 name: "OS Image causing error determining license terms for compute", 1375 edits: editFunctions{erroringLicenseTermsOSImageCompute, validInstanceTypes}, 1376 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: could not determine if the license terms for the marketplace image have been accepted: error`, 1377 }, 1378 { 1379 name: "OS Image with unaccepted license terms for compute", 1380 edits: editFunctions{unacceptedLicenseTermsOSImageCompute, validInstanceTypes}, 1381 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: the license terms for the marketplace image have not been accepted`, 1382 }, 1383 { 1384 name: "OS Image with wrong HyperV generation for compute", 1385 edits: editFunctions{erroringGenerationOsImageCompute, validInstanceTypes}, 1386 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*`, 1387 }, 1388 { 1389 name: "OS Image with wrong HyperV generation for compute from default MachinePool", 1390 edits: editFunctions{erroringGenerationOsImageDefaultMachine, validInstanceTypes}, 1391 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*`, 1392 }, 1393 { 1394 name: "OS Image with wrong HyperV generation for inherited instance type for compute", 1395 edits: editFunctions{erroringGenerationOsImageCompute, defaultMachineInstanceTypeHyperVGen1}, 1396 errorMsg: `compute\[0\].platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*`, 1397 }, 1398 // Marketplace definition for ControlPlane nodes 1399 { 1400 name: "Valid OS Image controlPlane", 1401 edits: editFunctions{validOSImageControlPlane, validInstanceTypes}, 1402 }, 1403 { 1404 name: "Invalid OS Image controlPlane", 1405 edits: editFunctions{invalidOSImageControlPlane, validOSImageCompute, validInstanceTypes}, 1406 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: not found`, 1407 }, 1408 { 1409 name: "Invalid OS Image controlPlane from default MachinePool", 1410 edits: editFunctions{invalidOSImageDefaultMachine, validOSImageCompute, validInstanceTypes}, 1411 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: not found`, 1412 }, 1413 { 1414 name: "OS Image causing error determining license terms for controlPlane", 1415 edits: editFunctions{erroringLicenseTermsOSImageControlPlane, validInstanceTypes}, 1416 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: could not determine if the license terms for the marketplace image have been accepted: error`, 1417 }, 1418 { 1419 name: "OS Image with unaccepted license terms for controlPlane", 1420 edits: editFunctions{unacceptedLicenseTermsOSImageControlPlane, validInstanceTypes}, 1421 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: the license terms for the marketplace image have not been accepted`, 1422 }, 1423 { 1424 name: "OS Image with wrong HyperV generation for controlPlane", 1425 edits: editFunctions{erroringGenerationOsImageControlPlane, validInstanceTypes, controlPlaneInstanceTypeHyperVGen1}, 1426 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*`, 1427 }, 1428 { 1429 name: "OS Image with wrong HyperV generation for controlPlane from default MachinePool", 1430 edits: editFunctions{erroringGenerationOsImageControlPlane, defaultMachineInstanceTypeHyperVGen1}, 1431 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*`, 1432 }, 1433 { 1434 name: "OS Image with wrong HyperV generation for inherited instance type for controlPlane", 1435 edits: editFunctions{erroringGenerationOsImageControlPlane, defaultMachineInstanceTypeHyperVGen1}, 1436 errorMsg: `controlPlane.platform.azure.osImage: Invalid value: .*: instance type supports HyperVGenerations \[(V[12])\] but the specified image is for HyperVGeneration [^\\1].*`, 1437 }, 1438 // Marketplace definition from Default Machine Pool 1439 { 1440 name: "Valid OS Image from default MachinePool", 1441 edits: editFunctions{validOSImageDefaultMachine, validInstanceTypes}, 1442 }, 1443 { 1444 name: "Valid OS Image when default MachinePool not valid", 1445 edits: editFunctions{invalidOSImageDefaultMachine, validOSImageControlPlane, validOSImageCompute, validInstanceTypes}, 1446 }, 1447 { 1448 name: "Invalid OS Image from default MachinePool", 1449 edits: editFunctions{invalidOSImageDefaultMachine, validInstanceTypes}, 1450 errorMsg: `^\[controlPlane.platform.azure.osImage: Invalid value: .*: not found, compute\[0\].platform.azure.osImage: Invalid value: .*: not found\]$`, 1451 }, 1452 { 1453 name: "OS Image causing error determining license terms from default MachinePool", 1454 edits: editFunctions{erroringLicenseTermsOSImageDefaultMachine, validInstanceTypes}, 1455 errorMsg: `^\[controlPlane.platform.azure.osImage: Invalid value: .*: could not determine if the license terms for the marketplace image have been accepted: error, compute\[0\].platform.azure.osImage: Invalid value: .*: could not determine if the license terms for the marketplace image have been accepted: error\]$`, 1456 }, 1457 { 1458 name: "OS Image with unaccepted license terms from default MachinePool", 1459 edits: editFunctions{unacceptedLicenseTermsOSImageDefaultMachine, validInstanceTypes}, 1460 errorMsg: `^\[controlPlane.platform.azure.osImage: Invalid value: .*: the license terms for the marketplace image have not been accepted, compute\[0\].platform.azure.osImage: Invalid value: .*: the license terms for the marketplace image have not been accepted\]$`, 1461 }, 1462 } 1463 1464 mockCtrl := gomock.NewController(t) 1465 defer mockCtrl.Finish() 1466 1467 azureClient := mock.NewMockAPI(mockCtrl) 1468 1469 // Marketplace images 1470 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, validOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, nil).AnyTimes() 1471 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, validOSImageSKU).Return(true, nil).AnyTimes() 1472 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, invalidOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, fmt.Errorf("not found")).AnyTimes() 1473 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, erroringLicenseTermsOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, nil).AnyTimes() 1474 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, erroringLicenseTermsOSImageSKU).Return(false, fmt.Errorf("error")).AnyTimes() 1475 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, unacceptedLicenseTermsOSImageSKU, validOSImageVersion).Return(marketplaceImageAPIResult, nil).AnyTimes() 1476 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, unacceptedLicenseTermsOSImageSKU).Return(false, nil).AnyTimes() 1477 azureClient.EXPECT().GetMarketplaceImage(gomock.Any(), validRegion, validOSImagePublisher, validOSImageOffer, erroringOSImageSKU, validOSImageVersion).Return(azenc.VirtualMachineImage{ 1478 VirtualMachineImageProperties: &azenc.VirtualMachineImageProperties{ 1479 HyperVGeneration: azenc.HyperVGenerationTypesV2, 1480 }, 1481 }, nil).AnyTimes() 1482 azureClient.EXPECT().AreMarketplaceImageTermsAccepted(gomock.Any(), validOSImagePublisher, validOSImageOffer, erroringOSImageSKU).Return(true, nil).AnyTimes() 1483 1484 // VM Capabilities 1485 for key, value := range vmCapabilities { 1486 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), key, validRegion).Return(value, nil).AnyTimes() 1487 } 1488 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), "Dne_D2_v4", validRegion).Return(nil, fmt.Errorf("not found in region centralus")).AnyTimes() 1489 azureClient.EXPECT().GetVMCapabilities(gomock.Any(), gomock.Any(), gomock.Any()).Return(vmCapabilities["Standard_D8s_v3"], nil).AnyTimes() 1490 1491 // HyperVGenerations 1492 azureClient.EXPECT().GetHyperVGenerationVersion(gomock.Any(), gomock.Any(), gomock.Any(), "V1").Return("", fmt.Errorf("instance type Standard_D8s_v3 supports HyperVGenerations [V2] but the specified image is for HyperVGeneration V1; to correct this issue either specify a compatible instance type or change the HyperVGeneration for the image by using a different SKU")).AnyTimes() 1493 azureClient.EXPECT().GetHyperVGenerationVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("V2", nil).AnyTimes() 1494 1495 for _, tc := range cases { 1496 t.Run(tc.name, func(t *testing.T) { 1497 editedInstallConfig := validInstallConfig() 1498 for _, edit := range tc.edits { 1499 edit(editedInstallConfig) 1500 } 1501 aggregatedErrors := validateMarketplaceImages(azureClient, editedInstallConfig) 1502 err := aggregatedErrors.ToAggregate() 1503 if tc.errorMsg != "" { 1504 assert.Regexp(t, tc.errorMsg, err) 1505 } else { 1506 assert.NoError(t, err) 1507 } 1508 }) 1509 } 1510 } 1511 1512 func TestAzureStackDiskType(t *testing.T) { 1513 const unsupportedDiskType = "StandardSSD_LRS" 1514 1515 validDefaultDiskType := func(ic *types.InstallConfig) { 1516 ic.Azure.DefaultMachinePlatform.DiskType = "Premium_LRS" 1517 } 1518 invalidDefaultDiskType := func(ic *types.InstallConfig) { 1519 ic.Azure.DefaultMachinePlatform.DiskType = unsupportedDiskType 1520 } 1521 validControlPlaneDiskType := func(ic *types.InstallConfig) { 1522 ic.ControlPlane.Platform.Azure.DiskType = "Standard_LRS" 1523 } 1524 invalidControlPlaneDiskType := func(ic *types.InstallConfig) { 1525 ic.ControlPlane.Platform.Azure.DiskType = unsupportedDiskType 1526 } 1527 validComputeDiskType := func(ic *types.InstallConfig) { 1528 ic.Compute[0].Platform.Azure.DiskType = "Standard_LRS" 1529 } 1530 invalidComputeDiskType := func(ic *types.InstallConfig) { 1531 ic.Compute[0].Platform.Azure.DiskType = unsupportedDiskType 1532 } 1533 1534 cases := []struct { 1535 name string 1536 edits editFunctions 1537 errorMsg string 1538 }{ 1539 { 1540 name: "Valid defaultMachinePlatform DiskType", 1541 edits: editFunctions{validDefaultDiskType}, 1542 errorMsg: "", 1543 }, 1544 { 1545 name: "Invalid defaultMachinePlatform DiskType", 1546 edits: editFunctions{invalidDefaultDiskType}, 1547 errorMsg: `\[controlPlane.platform.azure.OSDisk.diskType: Invalid value: "StandardSSD_LRS": disk format not supported. Must be one of \[Premium_LRS Standard_LRS\] compute\[0\].platform.azure.OSDisk.diskType: Invalid value: "StandardSSD_LRS": disk format not supported. Must be one of \[Premium_LRS Standard_LRS\]\]`, 1548 }, 1549 { 1550 name: "Valid controlPlane DiskType", 1551 edits: editFunctions{validControlPlaneDiskType}, 1552 errorMsg: "", 1553 }, 1554 { 1555 name: "Invalid controlPlane DiskType", 1556 edits: editFunctions{invalidControlPlaneDiskType}, 1557 errorMsg: `\[controlPlane.platform.azure.OSDisk.diskType: Invalid value: "StandardSSD_LRS": disk format not supported. Must be one of \[Premium_LRS Standard_LRS\]\]`, 1558 }, 1559 { 1560 name: "Valid compute DiskType", 1561 edits: editFunctions{validComputeDiskType}, 1562 errorMsg: "", 1563 }, 1564 { 1565 name: "Invalid compute DiskType", 1566 edits: editFunctions{invalidComputeDiskType}, 1567 errorMsg: `\[compute\[0\].platform.azure.OSDisk.diskType: Invalid value: "StandardSSD_LRS": disk format not supported. Must be one of \[Premium_LRS Standard_LRS\]\]`, 1568 }, 1569 } 1570 1571 mockCtrl := gomock.NewController(t) 1572 defer mockCtrl.Finish() 1573 1574 azureClient := mock.NewMockAPI(mockCtrl) 1575 1576 for _, tc := range cases { 1577 t.Run(tc.name, func(t *testing.T) { 1578 editedInstallConfig := validInstallConfig() 1579 for _, edit := range tc.edits { 1580 edit(editedInstallConfig) 1581 } 1582 1583 aggregatedErrors := validateAzureStackDiskType(azureClient, editedInstallConfig) 1584 if tc.errorMsg != "" { 1585 assert.Regexp(t, tc.errorMsg, aggregatedErrors) 1586 } else { 1587 assert.NoError(t, aggregatedErrors.ToAggregate()) 1588 } 1589 }) 1590 } 1591 }