sigs.k8s.io/cluster-api-provider-azure@v1.17.0/api/v1beta1/azuremachine_validation_test.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1beta1
    18  
    19  import (
    20  	"crypto/rand"
    21  	"crypto/rsa"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"testing"
    25  
    26  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    27  	"github.com/google/uuid"
    28  	. "github.com/onsi/gomega"
    29  	"golang.org/x/crypto/ssh"
    30  	"k8s.io/apimachinery/pkg/util/validation/field"
    31  	"k8s.io/utils/ptr"
    32  )
    33  
    34  func TestAzureMachine_ValidateSSHKey(t *testing.T) {
    35  	tests := []struct {
    36  		name    string
    37  		sshKey  string
    38  		wantErr bool
    39  	}{
    40  		{
    41  			name:    "valid ssh key",
    42  			sshKey:  generateSSHPublicKey(true),
    43  			wantErr: false,
    44  		},
    45  		{
    46  			name:    "invalid ssh key",
    47  			sshKey:  "invalid ssh key",
    48  			wantErr: true,
    49  		},
    50  		{
    51  			name:    "ssh key not base64 encoded",
    52  			sshKey:  generateSSHPublicKey(false),
    53  			wantErr: true,
    54  		},
    55  	}
    56  
    57  	for _, tc := range tests {
    58  		t.Run(tc.name, func(t *testing.T) {
    59  			g := NewWithT(t)
    60  			err := ValidateSSHKey(tc.sshKey, field.NewPath("sshPublicKey"))
    61  			if tc.wantErr {
    62  				g.Expect(err).NotTo(BeEmpty())
    63  			} else {
    64  				g.Expect(err).To(BeEmpty())
    65  			}
    66  		})
    67  	}
    68  }
    69  
    70  func generateSSHPublicKey(b64Enconded bool) string {
    71  	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
    72  	publicRsaKey, _ := ssh.NewPublicKey(&privateKey.PublicKey)
    73  	if b64Enconded {
    74  		return base64.StdEncoding.EncodeToString(ssh.MarshalAuthorizedKey(publicRsaKey))
    75  	}
    76  	return string(ssh.MarshalAuthorizedKey(publicRsaKey))
    77  }
    78  
    79  type osDiskTestInput struct {
    80  	name    string
    81  	wantErr bool
    82  	osDisk  OSDisk
    83  }
    84  
    85  func TestAzureMachine_ValidateOSDisk(t *testing.T) {
    86  	testcases := []osDiskTestInput{
    87  		{
    88  			name:    "valid os disk spec",
    89  			wantErr: false,
    90  			osDisk:  generateValidOSDisk(),
    91  		},
    92  		{
    93  			name:    "invalid os disk cache type",
    94  			wantErr: true,
    95  			osDisk:  createOSDiskWithCacheType("invalid_cache_type"),
    96  		},
    97  		{
    98  			name:    "valid ephemeral os disk spec",
    99  			wantErr: false,
   100  			osDisk: OSDisk{
   101  				DiskSizeGB:  ptr.To[int32](30),
   102  				CachingType: "None",
   103  				OSType:      "blah",
   104  				DiffDiskSettings: &DiffDiskSettings{
   105  					Option: string(armcompute.DiffDiskOptionsLocal),
   106  				},
   107  				ManagedDisk: &ManagedDiskParameters{
   108  					StorageAccountType: "Standard_LRS",
   109  				},
   110  			},
   111  		},
   112  		{
   113  			name:    "valid resourceDisk placement spec with option local",
   114  			wantErr: false,
   115  			osDisk: OSDisk{
   116  				DiskSizeGB:  ptr.To[int32](30),
   117  				CachingType: "None",
   118  				OSType:      "blah",
   119  				DiffDiskSettings: &DiffDiskSettings{
   120  					Option:    string(armcompute.DiffDiskOptionsLocal),
   121  					Placement: ptr.To(DiffDiskPlacementResourceDisk),
   122  				},
   123  				ManagedDisk: &ManagedDiskParameters{
   124  					StorageAccountType: "Standard_LRS",
   125  				},
   126  			},
   127  		},
   128  		{
   129  			name:    "valid resourceDisk placement spec requires option local",
   130  			wantErr: true,
   131  			osDisk: OSDisk{
   132  				DiskSizeGB:  ptr.To[int32](30),
   133  				CachingType: "None",
   134  				OSType:      "blah",
   135  				DiffDiskSettings: &DiffDiskSettings{
   136  					Placement: ptr.To(DiffDiskPlacementResourceDisk),
   137  				},
   138  				ManagedDisk: &ManagedDiskParameters{
   139  					StorageAccountType: "Standard_LRS",
   140  				},
   141  			},
   142  		},
   143  		{
   144  			name:    "byoc encryption with ephemeral os disk spec",
   145  			wantErr: true,
   146  			osDisk: OSDisk{
   147  				DiskSizeGB:  ptr.To[int32](30),
   148  				CachingType: "None",
   149  				OSType:      "blah",
   150  				DiffDiskSettings: &DiffDiskSettings{
   151  					Option: string(armcompute.DiffDiskOptionsLocal),
   152  				},
   153  				ManagedDisk: &ManagedDiskParameters{
   154  					StorageAccountType: "Standard_LRS",
   155  					DiskEncryptionSet: &DiskEncryptionSetParameters{
   156  						ID: "disk-encryption-set",
   157  					},
   158  				},
   159  			},
   160  		},
   161  	}
   162  	testcases = append(testcases, generateNegativeTestCases()...)
   163  
   164  	for _, test := range testcases {
   165  		t.Run(test.name, func(t *testing.T) {
   166  			g := NewWithT(t)
   167  			err := ValidateOSDisk(test.osDisk, field.NewPath("osDisk"))
   168  			if test.wantErr {
   169  				g.Expect(err).NotTo(BeEmpty())
   170  			} else {
   171  				g.Expect(err).To(BeEmpty())
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func generateNegativeTestCases() []osDiskTestInput {
   178  	inputs := []osDiskTestInput{}
   179  	testCaseName := "invalid os disk spec"
   180  
   181  	invalidDiskSpecs := []OSDisk{
   182  		{},
   183  		{
   184  			DiskSizeGB: ptr.To[int32](0),
   185  			OSType:     "blah",
   186  		},
   187  		{
   188  			DiskSizeGB: ptr.To[int32](-10),
   189  			OSType:     "blah",
   190  		},
   191  		{
   192  			DiskSizeGB: ptr.To[int32](2050),
   193  			OSType:     "blah",
   194  		},
   195  		{
   196  			DiskSizeGB: ptr.To[int32](20),
   197  			OSType:     "",
   198  		},
   199  		{
   200  			DiskSizeGB:  ptr.To[int32](30),
   201  			OSType:      "blah",
   202  			ManagedDisk: &ManagedDiskParameters{},
   203  		},
   204  		{
   205  			DiskSizeGB: ptr.To[int32](30),
   206  			OSType:     "blah",
   207  			ManagedDisk: &ManagedDiskParameters{
   208  				StorageAccountType: "",
   209  			},
   210  		},
   211  		{
   212  			DiskSizeGB: ptr.To[int32](30),
   213  			OSType:     "blah",
   214  			ManagedDisk: &ManagedDiskParameters{
   215  				StorageAccountType: "invalid_type",
   216  			},
   217  		},
   218  		{
   219  			DiskSizeGB: ptr.To[int32](30),
   220  			OSType:     "blah",
   221  			ManagedDisk: &ManagedDiskParameters{
   222  				StorageAccountType: "Premium_LRS",
   223  			},
   224  			DiffDiskSettings: &DiffDiskSettings{
   225  				Option: string(armcompute.DiffDiskOptionsLocal),
   226  			},
   227  		},
   228  	}
   229  
   230  	for i, input := range invalidDiskSpecs {
   231  		inputs = append(inputs, osDiskTestInput{
   232  			name:    fmt.Sprintf("%s-%d", testCaseName, i),
   233  			wantErr: true,
   234  			osDisk:  input,
   235  		})
   236  	}
   237  
   238  	return inputs
   239  }
   240  
   241  func generateValidOSDisk() OSDisk {
   242  	return OSDisk{
   243  		DiskSizeGB: ptr.To[int32](30),
   244  		OSType:     LinuxOS,
   245  		ManagedDisk: &ManagedDiskParameters{
   246  			StorageAccountType: "Premium_LRS",
   247  		},
   248  		CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   249  	}
   250  }
   251  
   252  func createOSDiskWithCacheType(cacheType string) OSDisk {
   253  	osDisk := generateValidOSDisk()
   254  	osDisk.CachingType = cacheType
   255  	return osDisk
   256  }
   257  
   258  func TestAzureMachine_ValidateDataDisks(t *testing.T) {
   259  	testcases := []struct {
   260  		name    string
   261  		disks   []DataDisk
   262  		wantErr bool
   263  	}{
   264  		{
   265  			name:    "valid nil data disks",
   266  			disks:   nil,
   267  			wantErr: false,
   268  		},
   269  		{
   270  			name:    "valid empty data disks",
   271  			disks:   []DataDisk{},
   272  			wantErr: false,
   273  		},
   274  		{
   275  			name: "valid disks",
   276  			disks: []DataDisk{
   277  				{
   278  					NameSuffix:  "my_disk",
   279  					DiskSizeGB:  64,
   280  					Lun:         ptr.To[int32](0),
   281  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   282  				},
   283  				{
   284  					NameSuffix:  "my_other_disk",
   285  					DiskSizeGB:  64,
   286  					Lun:         ptr.To[int32](1),
   287  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   288  				},
   289  			},
   290  			wantErr: false,
   291  		},
   292  		{
   293  			name: "duplicate names",
   294  			disks: []DataDisk{
   295  				{
   296  					NameSuffix:  "disk",
   297  					DiskSizeGB:  64,
   298  					Lun:         ptr.To[int32](0),
   299  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   300  				},
   301  				{
   302  					NameSuffix:  "disk",
   303  					DiskSizeGB:  64,
   304  					Lun:         ptr.To[int32](1),
   305  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   306  				},
   307  			},
   308  			wantErr: true,
   309  		},
   310  		{
   311  			name: "duplicate LUNs",
   312  			disks: []DataDisk{
   313  				{
   314  					NameSuffix:  "my_disk",
   315  					DiskSizeGB:  64,
   316  					Lun:         ptr.To[int32](0),
   317  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   318  				},
   319  				{
   320  					NameSuffix:  "my_other_disk",
   321  					DiskSizeGB:  64,
   322  					Lun:         ptr.To[int32](0),
   323  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   324  				},
   325  			},
   326  			wantErr: true,
   327  		},
   328  		{
   329  			name: "invalid disk size",
   330  			disks: []DataDisk{
   331  				{
   332  					NameSuffix:  "my_disk",
   333  					DiskSizeGB:  0,
   334  					Lun:         ptr.To[int32](0),
   335  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   336  				},
   337  			},
   338  			wantErr: true,
   339  		},
   340  		{
   341  			name: "empty name",
   342  			disks: []DataDisk{
   343  				{
   344  					NameSuffix:  "",
   345  					DiskSizeGB:  0,
   346  					Lun:         ptr.To[int32](0),
   347  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   348  				},
   349  			},
   350  			wantErr: true,
   351  		},
   352  		{
   353  			name: "invalid disk cachingType",
   354  			disks: []DataDisk{
   355  				{
   356  					NameSuffix:  "my_disk",
   357  					DiskSizeGB:  64,
   358  					Lun:         ptr.To[int32](0),
   359  					CachingType: "invalidCacheType",
   360  				},
   361  			},
   362  			wantErr: true,
   363  		},
   364  		{
   365  			name: "valid disk cachingType",
   366  			disks: []DataDisk{
   367  				{
   368  					NameSuffix:  "my_disk",
   369  					DiskSizeGB:  64,
   370  					Lun:         ptr.To[int32](0),
   371  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   372  				},
   373  			},
   374  			wantErr: false,
   375  		},
   376  		{
   377  			name: "valid managed disk storage account type",
   378  			disks: []DataDisk{
   379  				{
   380  					NameSuffix: "my_disk_1",
   381  					DiskSizeGB: 64,
   382  					ManagedDisk: &ManagedDiskParameters{
   383  						StorageAccountType: "Premium_LRS",
   384  					},
   385  					Lun:         ptr.To[int32](0),
   386  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   387  				},
   388  				{
   389  					NameSuffix: "my_disk_2",
   390  					DiskSizeGB: 64,
   391  					ManagedDisk: &ManagedDiskParameters{
   392  						StorageAccountType: "Standard_LRS",
   393  					},
   394  					Lun:         ptr.To[int32](1),
   395  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   396  				},
   397  			},
   398  			wantErr: false,
   399  		},
   400  		{
   401  			name: "invalid managed disk storage account type",
   402  			disks: []DataDisk{
   403  				{
   404  					NameSuffix: "my_disk_1",
   405  					DiskSizeGB: 64,
   406  					ManagedDisk: &ManagedDiskParameters{
   407  						StorageAccountType: "invalid storage account",
   408  					},
   409  					Lun:         ptr.To[int32](0),
   410  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   411  				},
   412  			},
   413  			wantErr: true,
   414  		},
   415  		{
   416  			name: "valid combination of managed disk storage account type UltraSSD_LRS and cachingType None",
   417  			disks: []DataDisk{
   418  				{
   419  					NameSuffix: "my_disk_1",
   420  					DiskSizeGB: 64,
   421  					ManagedDisk: &ManagedDiskParameters{
   422  						StorageAccountType: string(armcompute.StorageAccountTypesUltraSSDLRS),
   423  					},
   424  					Lun:         ptr.To[int32](0),
   425  					CachingType: string(armcompute.CachingTypesNone),
   426  				},
   427  			},
   428  			wantErr: false,
   429  		},
   430  		{
   431  			name: "invalid combination of managed disk storage account type UltraSSD_LRS and cachingType ReadWrite",
   432  			disks: []DataDisk{
   433  				{
   434  					NameSuffix: "my_disk_1",
   435  					DiskSizeGB: 64,
   436  					ManagedDisk: &ManagedDiskParameters{
   437  						StorageAccountType: string(armcompute.StorageAccountTypesUltraSSDLRS),
   438  					},
   439  					Lun:         ptr.To[int32](0),
   440  					CachingType: string(armcompute.CachingTypesReadWrite),
   441  				},
   442  			},
   443  			wantErr: true,
   444  		},
   445  		{
   446  			name: "invalid combination of managed disk storage account type UltraSSD_LRS and cachingType ReadOnly",
   447  			disks: []DataDisk{
   448  				{
   449  					NameSuffix: "my_disk_1",
   450  					DiskSizeGB: 64,
   451  					ManagedDisk: &ManagedDiskParameters{
   452  						StorageAccountType: string(armcompute.StorageAccountTypesUltraSSDLRS),
   453  					},
   454  					Lun:         ptr.To[int32](0),
   455  					CachingType: string(armcompute.CachingTypesReadOnly),
   456  				},
   457  			},
   458  			wantErr: true,
   459  		},
   460  	}
   461  
   462  	for _, test := range testcases {
   463  		t.Run(test.name, func(t *testing.T) {
   464  			g := NewWithT(t)
   465  			err := ValidateDataDisks(test.disks, field.NewPath("dataDisks"))
   466  			if test.wantErr {
   467  				g.Expect(err).NotTo(BeEmpty())
   468  			} else {
   469  				g.Expect(err).To(BeEmpty())
   470  			}
   471  		})
   472  	}
   473  }
   474  
   475  func TestAzureMachine_ValidateSystemAssignedIdentity(t *testing.T) {
   476  	tests := []struct {
   477  		name               string
   478  		roleAssignmentName string
   479  		old                string
   480  		Identity           VMIdentity
   481  		wantErr            bool
   482  	}{
   483  		{
   484  			name:               "valid UUID",
   485  			roleAssignmentName: uuid.New().String(),
   486  			Identity:           VMIdentitySystemAssigned,
   487  			wantErr:            false,
   488  		},
   489  		{
   490  			name:               "wrong Identity type",
   491  			roleAssignmentName: uuid.New().String(),
   492  			Identity:           VMIdentityNone,
   493  			wantErr:            true,
   494  		},
   495  		{
   496  			name:               "not a valid UUID",
   497  			roleAssignmentName: "notaguid",
   498  			Identity:           VMIdentitySystemAssigned,
   499  			wantErr:            true,
   500  		},
   501  		{
   502  			name:               "empty",
   503  			roleAssignmentName: "",
   504  			Identity:           VMIdentitySystemAssigned,
   505  			wantErr:            true,
   506  		},
   507  		{
   508  			name:               "changed",
   509  			roleAssignmentName: uuid.New().String(),
   510  			old:                uuid.New().String(),
   511  			Identity:           VMIdentitySystemAssigned,
   512  			wantErr:            true,
   513  		},
   514  	}
   515  
   516  	for _, tc := range tests {
   517  		t.Run(tc.name, func(t *testing.T) {
   518  			g := NewWithT(t)
   519  			err := ValidateSystemAssignedIdentity(tc.Identity, tc.old, tc.roleAssignmentName, field.NewPath("sshPublicKey"))
   520  			if tc.wantErr {
   521  				g.Expect(err).NotTo(BeEmpty())
   522  			} else {
   523  				g.Expect(err).To(BeEmpty())
   524  			}
   525  		})
   526  	}
   527  }
   528  
   529  func TestAzureMachine_ValidateSystemAssignedIdentityRole(t *testing.T) {
   530  	tests := []struct {
   531  		name               string
   532  		Identity           VMIdentity
   533  		roleAssignmentName string
   534  		role               *SystemAssignedIdentityRole
   535  		wantErr            bool
   536  	}{
   537  		{
   538  			name:     "valid role",
   539  			Identity: VMIdentitySystemAssigned,
   540  			role: &SystemAssignedIdentityRole{
   541  				Name:         uuid.New().String(),
   542  				Scope:        "fake-scope",
   543  				DefinitionID: "fake-definition-id",
   544  			},
   545  		},
   546  		{
   547  			name:               "valid role using deprecated role assignment name",
   548  			Identity:           VMIdentitySystemAssigned,
   549  			roleAssignmentName: uuid.New().String(),
   550  			role: &SystemAssignedIdentityRole{
   551  				Scope:        "fake-scope",
   552  				DefinitionID: "fake-definition-id",
   553  			},
   554  		},
   555  		{
   556  			name:               "set both system assigned identity role and role assignment name",
   557  			Identity:           VMIdentitySystemAssigned,
   558  			roleAssignmentName: uuid.New().String(),
   559  			role: &SystemAssignedIdentityRole{
   560  				Name:         uuid.New().String(),
   561  				Scope:        "fake-scope",
   562  				DefinitionID: "fake-definition-id",
   563  			},
   564  			wantErr: true,
   565  		},
   566  		{
   567  			name:     "wrong Identity type",
   568  			Identity: VMIdentityUserAssigned,
   569  			role: &SystemAssignedIdentityRole{
   570  				Name:         uuid.New().String(),
   571  				Scope:        "fake-scope",
   572  				DefinitionID: "fake-definition-id",
   573  			},
   574  			wantErr: true,
   575  		},
   576  		{
   577  			name:     "missing scope",
   578  			Identity: VMIdentitySystemAssigned,
   579  			role: &SystemAssignedIdentityRole{
   580  				Name:         uuid.New().String(),
   581  				DefinitionID: "fake-definition-id",
   582  			},
   583  			wantErr: true,
   584  		},
   585  		{
   586  			name:     "missing definition id",
   587  			Identity: VMIdentitySystemAssigned,
   588  			role: &SystemAssignedIdentityRole{
   589  				Name:  uuid.New().String(),
   590  				Scope: "fake-scope",
   591  			},
   592  			wantErr: true,
   593  		},
   594  	}
   595  
   596  	for _, tc := range tests {
   597  		t.Run(tc.name, func(t *testing.T) {
   598  			g := NewWithT(t)
   599  			err := ValidateSystemAssignedIdentityRole(tc.Identity, tc.roleAssignmentName, tc.role, field.NewPath("systemAssignedIdentityRole"))
   600  			if tc.wantErr {
   601  				g.Expect(err).NotTo(BeEmpty())
   602  			} else {
   603  				g.Expect(err).To(BeEmpty())
   604  			}
   605  		})
   606  	}
   607  }
   608  
   609  func TestAzureMachine_ValidateUserAssignedIdentity(t *testing.T) {
   610  	tests := []struct {
   611  		name       string
   612  		idType     VMIdentity
   613  		identities []UserAssignedIdentity
   614  		wantErr    bool
   615  	}{
   616  		{
   617  			name:       "empty identity list",
   618  			idType:     VMIdentityUserAssigned,
   619  			identities: []UserAssignedIdentity{},
   620  			wantErr:    true,
   621  		},
   622  		{
   623  			name:   "invalid: providerID must start with slash",
   624  			idType: VMIdentityUserAssigned,
   625  			identities: []UserAssignedIdentity{
   626  				{
   627  					ProviderID: "subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265",
   628  				},
   629  			},
   630  			wantErr: true,
   631  		},
   632  		{
   633  			name:   "invalid: providerID must start with subscriptions or providers",
   634  			idType: VMIdentityUserAssigned,
   635  			identities: []UserAssignedIdentity{
   636  				{
   637  					ProviderID: "azure:///prescriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265",
   638  				},
   639  			},
   640  			wantErr: true,
   641  		},
   642  		{
   643  			name:   "valid",
   644  			idType: VMIdentityUserAssigned,
   645  			identities: []UserAssignedIdentity{
   646  				{
   647  					ProviderID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265",
   648  				},
   649  			},
   650  			wantErr: false,
   651  		},
   652  		{
   653  			name:   "valid with provider prefix",
   654  			idType: VMIdentityUserAssigned,
   655  			identities: []UserAssignedIdentity{
   656  				{
   657  					ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265",
   658  				},
   659  			},
   660  			wantErr: false,
   661  		},
   662  	}
   663  
   664  	for _, tc := range tests {
   665  		t.Run(tc.name, func(t *testing.T) {
   666  			g := NewWithT(t)
   667  			errs := ValidateUserAssignedIdentity(tc.idType, tc.identities, field.NewPath("userAssignedIdentities"))
   668  			if tc.wantErr {
   669  				g.Expect(errs).NotTo(BeEmpty())
   670  			} else {
   671  				g.Expect(errs).To(BeEmpty())
   672  			}
   673  		})
   674  	}
   675  }
   676  
   677  func TestAzureMachine_ValidateDataDisksUpdate(t *testing.T) {
   678  	tests := []struct {
   679  		name     string
   680  		disks    []DataDisk
   681  		oldDisks []DataDisk
   682  		wantErr  bool
   683  	}{
   684  		{
   685  			name:     "valid nil data disks",
   686  			disks:    nil,
   687  			oldDisks: nil,
   688  			wantErr:  false,
   689  		},
   690  		{
   691  			name:     "valid empty data disks",
   692  			disks:    []DataDisk{},
   693  			oldDisks: []DataDisk{},
   694  			wantErr:  false,
   695  		},
   696  		{
   697  			name: "valid data disk updates",
   698  			disks: []DataDisk{
   699  				{
   700  					NameSuffix: "my_disk",
   701  					DiskSizeGB: 64,
   702  					Lun:        ptr.To[int32](0),
   703  					ManagedDisk: &ManagedDiskParameters{
   704  						StorageAccountType: "Standard_LRS",
   705  					},
   706  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   707  				},
   708  				{
   709  					NameSuffix: "my_other_disk",
   710  					DiskSizeGB: 64,
   711  					Lun:        ptr.To[int32](1),
   712  					ManagedDisk: &ManagedDiskParameters{
   713  						StorageAccountType: "Standard_LRS",
   714  					},
   715  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   716  				},
   717  			},
   718  			oldDisks: []DataDisk{
   719  				{
   720  					NameSuffix: "my_disk",
   721  					DiskSizeGB: 64,
   722  					Lun:        ptr.To[int32](0),
   723  					ManagedDisk: &ManagedDiskParameters{
   724  						StorageAccountType: "Standard_LRS",
   725  					},
   726  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   727  				},
   728  				{
   729  					NameSuffix: "my_other_disk",
   730  					DiskSizeGB: 64,
   731  					Lun:        ptr.To[int32](1),
   732  					ManagedDisk: &ManagedDiskParameters{
   733  						StorageAccountType: "Standard_LRS",
   734  					},
   735  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   736  				},
   737  			},
   738  			wantErr: false,
   739  		},
   740  		{
   741  			name: "cannot update data disk fields after machine creation",
   742  			disks: []DataDisk{
   743  				{
   744  					NameSuffix: "my_disk_1",
   745  					DiskSizeGB: 64,
   746  					ManagedDisk: &ManagedDiskParameters{
   747  						StorageAccountType: "Standard_LRS",
   748  					},
   749  					Lun:         ptr.To[int32](0),
   750  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   751  				},
   752  			},
   753  			oldDisks: []DataDisk{
   754  				{
   755  					NameSuffix: "my_disk_1",
   756  					DiskSizeGB: 128,
   757  					ManagedDisk: &ManagedDiskParameters{
   758  						StorageAccountType: "Premium_LRS",
   759  					},
   760  					Lun:         ptr.To[int32](0),
   761  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   762  				},
   763  			},
   764  			wantErr: true,
   765  		},
   766  		{
   767  			name: "validate updates to optional fields",
   768  			disks: []DataDisk{
   769  				{
   770  					NameSuffix: "my_disk_1",
   771  					DiskSizeGB: 128,
   772  					ManagedDisk: &ManagedDiskParameters{
   773  						StorageAccountType: "Standard_LRS",
   774  					},
   775  					Lun: ptr.To[int32](0),
   776  				},
   777  			},
   778  			oldDisks: []DataDisk{
   779  				{
   780  					NameSuffix:  "my_disk_1",
   781  					DiskSizeGB:  128,
   782  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   783  				},
   784  			},
   785  			wantErr: true,
   786  		},
   787  		{
   788  			name: "data disks cannot be added after machine creation",
   789  			disks: []DataDisk{
   790  				{
   791  					NameSuffix: "my_disk_1",
   792  					DiskSizeGB: 64,
   793  					ManagedDisk: &ManagedDiskParameters{
   794  						StorageAccountType: "Standard_LRS",
   795  					},
   796  					Lun:         ptr.To[int32](0),
   797  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   798  				},
   799  			},
   800  			oldDisks: []DataDisk{
   801  				{
   802  					NameSuffix: "my_disk_1",
   803  					DiskSizeGB: 64,
   804  					ManagedDisk: &ManagedDiskParameters{
   805  						StorageAccountType: "Premium_LRS",
   806  					},
   807  					Lun:         ptr.To[int32](0),
   808  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   809  				},
   810  				{
   811  					NameSuffix: "my_disk_2",
   812  					DiskSizeGB: 64,
   813  					ManagedDisk: &ManagedDiskParameters{
   814  						StorageAccountType: "Premium_LRS",
   815  					},
   816  					Lun:         ptr.To[int32](2),
   817  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   818  				},
   819  			},
   820  			wantErr: true,
   821  		},
   822  		{
   823  			name: "data disks cannot be removed after machine creation",
   824  			disks: []DataDisk{
   825  				{
   826  					NameSuffix: "my_disk_1",
   827  					DiskSizeGB: 64,
   828  					ManagedDisk: &ManagedDiskParameters{
   829  						StorageAccountType: "Standard_LRS",
   830  					},
   831  					Lun:         ptr.To[int32](0),
   832  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   833  				},
   834  				{
   835  					NameSuffix: "my_disk_2",
   836  					DiskSizeGB: 64,
   837  					ManagedDisk: &ManagedDiskParameters{
   838  						StorageAccountType: "Premium_LRS",
   839  					},
   840  					Lun:         ptr.To[int32](2),
   841  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   842  				},
   843  			},
   844  			oldDisks: []DataDisk{
   845  				{
   846  					NameSuffix: "my_disk_1",
   847  					DiskSizeGB: 64,
   848  					ManagedDisk: &ManagedDiskParameters{
   849  						StorageAccountType: "Standard_LRS",
   850  					},
   851  					Lun:         ptr.To[int32](0),
   852  					CachingType: string(armcompute.PossibleCachingTypesValues()[0]),
   853  				},
   854  			},
   855  			wantErr: true,
   856  		},
   857  	}
   858  
   859  	for _, test := range tests {
   860  		t.Run(test.name, func(t *testing.T) {
   861  			g := NewWithT(t)
   862  			err := ValidateDataDisksUpdate(test.oldDisks, test.disks, field.NewPath("dataDisks"))
   863  			if test.wantErr {
   864  				g.Expect(err).NotTo(BeEmpty())
   865  			} else {
   866  				g.Expect(err).To(BeEmpty())
   867  			}
   868  		})
   869  	}
   870  }
   871  
   872  func TestAzureMachine_ValidateNetwork(t *testing.T) {
   873  	tests := []struct {
   874  		name                  string
   875  		subnetName            string
   876  		acceleratedNetworking *bool
   877  		networkInterfaces     []NetworkInterface
   878  		wantErr               bool
   879  	}{
   880  		{
   881  			name:                  "valid config with deprecated network fields",
   882  			subnetName:            "subnet1",
   883  			acceleratedNetworking: ptr.To(true),
   884  			networkInterfaces:     nil,
   885  			wantErr:               false,
   886  		},
   887  		{
   888  			name:                  "valid config with networkInterfaces fields",
   889  			subnetName:            "",
   890  			acceleratedNetworking: nil,
   891  			networkInterfaces: []NetworkInterface{{
   892  				SubnetName:            "subnet1",
   893  				AcceleratedNetworking: ptr.To(true),
   894  				PrivateIPConfigs:      1,
   895  			}},
   896  			wantErr: false,
   897  		},
   898  		{
   899  			name:                  "valid config with multiple networkInterfaces",
   900  			subnetName:            "",
   901  			acceleratedNetworking: nil,
   902  			networkInterfaces: []NetworkInterface{
   903  				{
   904  					SubnetName:            "subnet1",
   905  					AcceleratedNetworking: ptr.To(true),
   906  					PrivateIPConfigs:      1,
   907  				},
   908  				{
   909  					SubnetName:            "subnet2",
   910  					AcceleratedNetworking: ptr.To(true),
   911  					PrivateIPConfigs:      30,
   912  				},
   913  			},
   914  			wantErr: false,
   915  		},
   916  		{
   917  			name:                  "invalid config using both deprecated subnetName and networkInterfaces",
   918  			subnetName:            "subnet1",
   919  			acceleratedNetworking: nil,
   920  			networkInterfaces: []NetworkInterface{{
   921  				SubnetName:            "subnet1",
   922  				AcceleratedNetworking: nil,
   923  				PrivateIPConfigs:      1,
   924  			}},
   925  			wantErr: true,
   926  		},
   927  		{
   928  			name:                  "invalid config using both deprecated acceleratedNetworking and networkInterfaces",
   929  			subnetName:            "",
   930  			acceleratedNetworking: ptr.To(true),
   931  			networkInterfaces: []NetworkInterface{{
   932  				SubnetName:            "subnet1",
   933  				AcceleratedNetworking: ptr.To(true),
   934  				PrivateIPConfigs:      1,
   935  			}},
   936  			wantErr: true,
   937  		},
   938  		{
   939  			name:                  "invalid config setting privateIPConfigs to less than 1",
   940  			subnetName:            "",
   941  			acceleratedNetworking: nil,
   942  			networkInterfaces: []NetworkInterface{{
   943  				SubnetName:            "subnet1",
   944  				AcceleratedNetworking: ptr.To(true),
   945  				PrivateIPConfigs:      0,
   946  			}},
   947  			wantErr: true,
   948  		},
   949  	}
   950  
   951  	for _, test := range tests {
   952  		t.Run(test.name, func(t *testing.T) {
   953  			g := NewWithT(t)
   954  			err := ValidateNetwork(test.subnetName, test.acceleratedNetworking, test.networkInterfaces, field.NewPath("networkInterfaces"))
   955  			if test.wantErr {
   956  				g.Expect(err).NotTo(BeEmpty())
   957  			} else {
   958  				g.Expect(err).To(BeEmpty())
   959  			}
   960  		})
   961  	}
   962  }
   963  
   964  func TestAzureMachine_ValidateConfidentialCompute(t *testing.T) {
   965  	tests := []struct {
   966  		name            string
   967  		managedDisk     *ManagedDiskParameters
   968  		securityProfile *SecurityProfile
   969  		wantErr         bool
   970  	}{
   971  		{
   972  			name: "valid configuration without confidential compute",
   973  			managedDisk: &ManagedDiskParameters{
   974  				SecurityProfile: &VMDiskSecurityProfile{
   975  					SecurityEncryptionType: "",
   976  				},
   977  			},
   978  			securityProfile: nil,
   979  			wantErr:         false,
   980  		},
   981  		{
   982  			name: "valid configuration without confidential compute and host encryption enabled",
   983  			managedDisk: &ManagedDiskParameters{
   984  				SecurityProfile: &VMDiskSecurityProfile{
   985  					SecurityEncryptionType: "",
   986  				},
   987  			},
   988  			securityProfile: &SecurityProfile{
   989  				EncryptionAtHost: ptr.To(true),
   990  			},
   991  			wantErr: false,
   992  		},
   993  		{
   994  			name: "valid configuration with VMGuestStateOnly encryption and secure boot disabled",
   995  			managedDisk: &ManagedDiskParameters{
   996  				SecurityProfile: &VMDiskSecurityProfile{
   997  					SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly,
   998  				},
   999  			},
  1000  			securityProfile: &SecurityProfile{
  1001  				SecurityType: SecurityTypesConfidentialVM,
  1002  				UefiSettings: &UefiSettings{
  1003  					VTpmEnabled:       ptr.To(true),
  1004  					SecureBootEnabled: ptr.To(false),
  1005  				},
  1006  			},
  1007  			wantErr: false,
  1008  		},
  1009  		{
  1010  			name: "valid configuration with VMGuestStateOnly encryption and secure boot enabled",
  1011  			managedDisk: &ManagedDiskParameters{
  1012  				SecurityProfile: &VMDiskSecurityProfile{
  1013  					SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly,
  1014  				},
  1015  			},
  1016  			securityProfile: &SecurityProfile{
  1017  				SecurityType: SecurityTypesConfidentialVM,
  1018  				UefiSettings: &UefiSettings{
  1019  					VTpmEnabled:       ptr.To(true),
  1020  					SecureBootEnabled: ptr.To(true),
  1021  				},
  1022  			},
  1023  			wantErr: false,
  1024  		},
  1025  		{
  1026  			name: "valid configuration with VMGuestStateOnly encryption and EncryptionAtHost enabled",
  1027  			managedDisk: &ManagedDiskParameters{
  1028  				SecurityProfile: &VMDiskSecurityProfile{
  1029  					SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly,
  1030  				},
  1031  			},
  1032  			securityProfile: &SecurityProfile{
  1033  				EncryptionAtHost: ptr.To(true),
  1034  				SecurityType:     SecurityTypesConfidentialVM,
  1035  				UefiSettings: &UefiSettings{
  1036  					VTpmEnabled: ptr.To(true),
  1037  				},
  1038  			},
  1039  			wantErr: false,
  1040  		},
  1041  		{
  1042  			name: "valid configuration with DiskWithVMGuestState encryption",
  1043  			managedDisk: &ManagedDiskParameters{
  1044  				SecurityProfile: &VMDiskSecurityProfile{
  1045  					SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState,
  1046  				},
  1047  			},
  1048  			securityProfile: &SecurityProfile{
  1049  				SecurityType: SecurityTypesConfidentialVM,
  1050  				UefiSettings: &UefiSettings{
  1051  					SecureBootEnabled: ptr.To(true),
  1052  					VTpmEnabled:       ptr.To(true),
  1053  				},
  1054  			},
  1055  			wantErr: false,
  1056  		},
  1057  		{
  1058  			name: "invalid configuration with DiskWithVMGuestState encryption and EncryptionAtHost enabled",
  1059  			managedDisk: &ManagedDiskParameters{
  1060  				SecurityProfile: &VMDiskSecurityProfile{
  1061  					SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState,
  1062  				},
  1063  			},
  1064  			securityProfile: &SecurityProfile{
  1065  				EncryptionAtHost: ptr.To(true),
  1066  			},
  1067  			wantErr: true,
  1068  		},
  1069  		{
  1070  			name: "invalid configuration with DiskWithVMGuestState encryption and vTPM disabled",
  1071  			managedDisk: &ManagedDiskParameters{
  1072  				SecurityProfile: &VMDiskSecurityProfile{
  1073  					SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState,
  1074  				},
  1075  			},
  1076  			securityProfile: &SecurityProfile{
  1077  				SecurityType: SecurityTypesConfidentialVM,
  1078  				UefiSettings: &UefiSettings{
  1079  					VTpmEnabled:       ptr.To(false),
  1080  					SecureBootEnabled: ptr.To(false),
  1081  				},
  1082  			},
  1083  			wantErr: true,
  1084  		},
  1085  		{
  1086  			name: "invalid configuration with DiskWithVMGuestState encryption and secure boot disabled",
  1087  			managedDisk: &ManagedDiskParameters{
  1088  				SecurityProfile: &VMDiskSecurityProfile{
  1089  					SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState,
  1090  				},
  1091  			},
  1092  			securityProfile: &SecurityProfile{
  1093  				SecurityType: SecurityTypesConfidentialVM,
  1094  				UefiSettings: &UefiSettings{
  1095  					VTpmEnabled:       ptr.To(true),
  1096  					SecureBootEnabled: ptr.To(false),
  1097  				},
  1098  			},
  1099  			wantErr: true,
  1100  		},
  1101  		{
  1102  			name: "invalid configuration with DiskWithVMGuestState encryption and SecurityType not set to ConfidentialVM",
  1103  			managedDisk: &ManagedDiskParameters{
  1104  				SecurityProfile: &VMDiskSecurityProfile{
  1105  					SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState,
  1106  				},
  1107  			},
  1108  			securityProfile: &SecurityProfile{
  1109  				UefiSettings: &UefiSettings{
  1110  					VTpmEnabled:       ptr.To(true),
  1111  					SecureBootEnabled: ptr.To(true),
  1112  				},
  1113  			},
  1114  			wantErr: true,
  1115  		},
  1116  		{
  1117  			name: "invalid configuration with VMGuestStateOnly encryption and SecurityType not set to ConfidentialVM",
  1118  			managedDisk: &ManagedDiskParameters{
  1119  				SecurityProfile: &VMDiskSecurityProfile{
  1120  					SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly,
  1121  				},
  1122  			},
  1123  			securityProfile: &SecurityProfile{
  1124  				UefiSettings: &UefiSettings{
  1125  					VTpmEnabled:       ptr.To(true),
  1126  					SecureBootEnabled: ptr.To(true),
  1127  				},
  1128  			},
  1129  			wantErr: true,
  1130  		},
  1131  	}
  1132  
  1133  	for _, tc := range tests {
  1134  		t.Run(tc.name, func(t *testing.T) {
  1135  			g := NewWithT(t)
  1136  			err := ValidateConfidentialCompute(tc.managedDisk, tc.securityProfile, field.NewPath("securityProfile"))
  1137  			if tc.wantErr {
  1138  				g.Expect(err).NotTo(BeEmpty())
  1139  			} else {
  1140  				g.Expect(err).To(BeEmpty())
  1141  			}
  1142  		})
  1143  	}
  1144  }