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  }