sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremachinetemplate_webhook_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  	"context"
    21  	"testing"
    22  
    23  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    24  	. "github.com/onsi/gomega"
    25  	admissionv1 "k8s.io/api/admission/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/utils/ptr"
    28  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    29  )
    30  
    31  func TestAzureMachineTemplate_ValidateCreate(t *testing.T) {
    32  	tests := []struct {
    33  		name            string
    34  		machineTemplate *AzureMachineTemplate
    35  		wantErr         bool
    36  	}{
    37  		{
    38  			name: "azuremachinetemplate with marketplane image - full",
    39  			machineTemplate: createAzureMachineTemplateFromMachine(
    40  				createMachineWithMarketPlaceImage("PUB1234", "OFFER1234", "SKU1234", "1.0.0"),
    41  			),
    42  			wantErr: false,
    43  		},
    44  		{
    45  			name: "azuremachinetemplate with marketplace image - missing publisher",
    46  			machineTemplate: createAzureMachineTemplateFromMachine(
    47  				createMachineWithMarketPlaceImage("", "OFFER1234", "SKU1234", "1.0.0"),
    48  			),
    49  			wantErr: true,
    50  		},
    51  		{
    52  			name: "azuremachinetemplate with shared gallery image - full",
    53  			machineTemplate: createAzureMachineTemplateFromMachine(
    54  				createMachineWithSharedImage("SUB123", "RG123", "NAME123", "GALLERY1", "1.0.0"),
    55  			),
    56  			wantErr: false,
    57  		},
    58  		{
    59  			name: "azuremachinetemplate with marketplace image - missing subscription",
    60  			machineTemplate: createAzureMachineTemplateFromMachine(
    61  				createMachineWithSharedImage("", "RG123", "NAME123", "GALLERY2", "1.0.0"),
    62  			),
    63  			wantErr: true,
    64  		},
    65  		{
    66  			name: "azuremachinetemplate with image by - with id",
    67  			machineTemplate: createAzureMachineTemplateFromMachine(
    68  				createMachineWithImageByID("ID123"),
    69  			),
    70  			wantErr: false,
    71  		},
    72  		{
    73  			name: "azuremachinetemplate with image by - without id",
    74  			machineTemplate: createAzureMachineTemplateFromMachine(
    75  				createMachineWithImageByID(""),
    76  			),
    77  			wantErr: true,
    78  		},
    79  		{
    80  			name: "azuremachinetemplate with valid SSHPublicKey",
    81  			machineTemplate: createAzureMachineTemplateFromMachine(
    82  				createMachineWithSSHPublicKey(validSSHPublicKey),
    83  			),
    84  			wantErr: false,
    85  		},
    86  		{
    87  			name: "azuremachinetemplate without SSHPublicKey",
    88  			machineTemplate: createAzureMachineTemplateFromMachine(
    89  				createMachineWithSSHPublicKey(""),
    90  			),
    91  			wantErr: true,
    92  		},
    93  		{
    94  			name: "azuremachinetemplate with invalid SSHPublicKey",
    95  			machineTemplate: createAzureMachineTemplateFromMachine(
    96  				createMachineWithSSHPublicKey("invalid ssh key"),
    97  			),
    98  			wantErr: true,
    99  		},
   100  		{
   101  			name: "azuremachinetemplate with list of user-assigned identities",
   102  			machineTemplate: createAzureMachineTemplateFromMachine(
   103  				createMachineWithUserAssignedIdentities([]UserAssignedIdentity{
   104  					{ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-09091-control-plane-f1b2c"},
   105  					{ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-09091-control-plane-9a8b7"},
   106  				}),
   107  			),
   108  			wantErr: false,
   109  		},
   110  		{
   111  			name: "azuremachinetemplate with empty list of user-assigned identities",
   112  			machineTemplate: createAzureMachineTemplateFromMachine(
   113  				createMachineWithUserAssignedIdentities([]UserAssignedIdentity{}),
   114  			),
   115  			wantErr: true,
   116  		},
   117  		{
   118  			name: "azuremachinetemplate with valid osDisk cache type",
   119  			machineTemplate: createAzureMachineTemplateFromMachine(
   120  				createMachineWithOsDiskCacheType(string(armcompute.PossibleCachingTypesValues()[1])),
   121  			),
   122  			wantErr: false,
   123  		},
   124  		{
   125  			name: "azuremachinetemplate with invalid osDisk cache type",
   126  			machineTemplate: createAzureMachineTemplateFromMachine(
   127  				createMachineWithOsDiskCacheType("invalid_cache_type"),
   128  			),
   129  			wantErr: true,
   130  		},
   131  		{
   132  			name:            "azuremachinetemplate with SystemAssignedIdentityRoleName",
   133  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithSystemAssignedIdentityRoleName()),
   134  			wantErr:         true,
   135  		},
   136  		{
   137  			name:            "azuremachinetemplate without SystemAssignedIdentityRoleName",
   138  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithoutSystemAssignedIdentityRoleName()),
   139  			wantErr:         false,
   140  		},
   141  		{
   142  			name:            "azuremachinetemplate with RoleAssignmentName",
   143  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithRoleAssignmentName()),
   144  			wantErr:         true,
   145  		},
   146  		{
   147  			name:            "azuremachinetemplate without RoleAssignmentName",
   148  			machineTemplate: createAzureMachineTemplateFromMachine(createMachineWithoutRoleAssignmentName()),
   149  			wantErr:         false,
   150  		},
   151  		{
   152  			name: "azuremachinetemplate with network interfaces > 0 and subnet name",
   153  			machineTemplate: createAzureMachineTemplateFromMachine(
   154  				createMachineWithNetworkConfig(
   155  					"test-subnet",
   156  					nil,
   157  					[]NetworkInterface{
   158  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   159  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   160  					},
   161  				),
   162  			),
   163  			wantErr: true,
   164  		},
   165  		{
   166  			name: "azuremachinetemplate with network interfaces > 0 and no subnet name",
   167  			machineTemplate: createAzureMachineTemplateFromMachine(
   168  				createMachineWithNetworkConfig(
   169  					"",
   170  					nil,
   171  					[]NetworkInterface{
   172  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   173  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   174  					},
   175  				),
   176  			),
   177  			wantErr: false,
   178  		},
   179  		{
   180  			name: "azuremachinetemplate with network interfaces > 0 and AcceleratedNetworking not nil",
   181  			machineTemplate: createAzureMachineTemplateFromMachine(
   182  				createMachineWithNetworkConfig(
   183  					"",
   184  					ptr.To(true),
   185  					[]NetworkInterface{
   186  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   187  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   188  					},
   189  				),
   190  			),
   191  			wantErr: true,
   192  		},
   193  		{
   194  			name: "azuremachinetemplate with network interfaces > 0 and AcceleratedNetworking nil",
   195  			machineTemplate: createAzureMachineTemplateFromMachine(
   196  				createMachineWithNetworkConfig(
   197  					"",
   198  					nil,
   199  					[]NetworkInterface{
   200  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   201  						{SubnetName: "subnet2", PrivateIPConfigs: 1},
   202  					},
   203  				),
   204  			),
   205  			wantErr: false,
   206  		},
   207  		{
   208  			name: "azuremachinetemplate with network interfaces and PrivateIPConfigs < 1",
   209  			machineTemplate: createAzureMachineTemplateFromMachine(
   210  				createMachineWithNetworkConfig(
   211  					"",
   212  					nil,
   213  					[]NetworkInterface{
   214  						{SubnetName: "subnet1", PrivateIPConfigs: 0},
   215  						{SubnetName: "subnet2", PrivateIPConfigs: -1},
   216  					},
   217  				),
   218  			),
   219  			wantErr: true,
   220  		},
   221  		{
   222  			name: "azuremachinetemplate with network interfaces and PrivateIPConfigs >= 1",
   223  			machineTemplate: createAzureMachineTemplateFromMachine(
   224  				createMachineWithNetworkConfig(
   225  					"",
   226  					nil,
   227  					[]NetworkInterface{
   228  						{SubnetName: "subnet1", PrivateIPConfigs: 1},
   229  						{SubnetName: "subnet2", PrivateIPConfigs: 2},
   230  					},
   231  				),
   232  			),
   233  			wantErr: false,
   234  		},
   235  	}
   236  
   237  	for _, test := range tests {
   238  		test := test
   239  		t.Run(test.name, func(t *testing.T) {
   240  			t.Parallel()
   241  			g := NewWithT(t)
   242  			ctx := context.Background()
   243  			_, err := test.machineTemplate.ValidateCreate(ctx, test.machineTemplate)
   244  			if test.wantErr {
   245  				g.Expect(err).To(HaveOccurred())
   246  			} else {
   247  				g.Expect(err).NotTo(HaveOccurred())
   248  			}
   249  		})
   250  	}
   251  }
   252  
   253  func TestAzureMachineTemplate_ValidateUpdate(t *testing.T) {
   254  	failureDomain := "domaintest"
   255  
   256  	tests := []struct {
   257  		name        string
   258  		oldTemplate *AzureMachineTemplate
   259  		template    *AzureMachineTemplate
   260  		wantErr     bool
   261  	}{
   262  		{
   263  			name: "AzureMachineTemplate with immutable spec",
   264  			oldTemplate: &AzureMachineTemplate{
   265  				Spec: AzureMachineTemplateSpec{
   266  					Template: AzureMachineTemplateResource{
   267  						Spec: AzureMachineSpec{
   268  							VMSize:        "size",
   269  							FailureDomain: &failureDomain,
   270  							OSDisk: OSDisk{
   271  								OSType:     "type",
   272  								DiskSizeGB: ptr.To[int32](11),
   273  							},
   274  							DataDisks:    []DataDisk{},
   275  							SSHPublicKey: "",
   276  						},
   277  					},
   278  				},
   279  			},
   280  			template: &AzureMachineTemplate{
   281  				Spec: AzureMachineTemplateSpec{
   282  					Template: AzureMachineTemplateResource{
   283  						Spec: AzureMachineSpec{
   284  							VMSize:        "size1",
   285  							FailureDomain: &failureDomain,
   286  							OSDisk: OSDisk{
   287  								OSType:     "type",
   288  								DiskSizeGB: ptr.To[int32](11),
   289  							},
   290  							DataDisks:    []DataDisk{},
   291  							SSHPublicKey: "fake ssh key",
   292  						},
   293  					},
   294  				},
   295  			},
   296  			wantErr: true,
   297  		},
   298  		{
   299  			name: "AzureMachineTemplate with mutable metadata",
   300  			oldTemplate: &AzureMachineTemplate{
   301  				Spec: AzureMachineTemplateSpec{
   302  					Template: AzureMachineTemplateResource{
   303  						Spec: AzureMachineSpec{
   304  							VMSize:        "size",
   305  							FailureDomain: &failureDomain,
   306  							OSDisk: OSDisk{
   307  								OSType:     "type",
   308  								DiskSizeGB: ptr.To[int32](11),
   309  							},
   310  							DataDisks:    []DataDisk{},
   311  							SSHPublicKey: "fake ssh key",
   312  						},
   313  					},
   314  				},
   315  				ObjectMeta: metav1.ObjectMeta{
   316  					Name: "OldTemplate",
   317  				},
   318  			},
   319  			template: &AzureMachineTemplate{
   320  				Spec: AzureMachineTemplateSpec{
   321  					Template: AzureMachineTemplateResource{
   322  						Spec: AzureMachineSpec{
   323  							VMSize:        "size",
   324  							FailureDomain: &failureDomain,
   325  							OSDisk: OSDisk{
   326  								OSType:     "type",
   327  								DiskSizeGB: ptr.To[int32](11),
   328  							},
   329  							DataDisks:    []DataDisk{},
   330  							SSHPublicKey: "fake ssh key",
   331  						},
   332  					},
   333  				},
   334  				ObjectMeta: metav1.ObjectMeta{
   335  					Name: "NewTemplate",
   336  				},
   337  			},
   338  			wantErr: false,
   339  		},
   340  		{
   341  			name: "AzureMachineTemplate with default mismatch",
   342  			oldTemplate: &AzureMachineTemplate{
   343  				Spec: AzureMachineTemplateSpec{
   344  					Template: AzureMachineTemplateResource{
   345  						Spec: AzureMachineSpec{
   346  							VMSize:        "size",
   347  							FailureDomain: &failureDomain,
   348  							OSDisk: OSDisk{
   349  								OSType:      "type",
   350  								DiskSizeGB:  ptr.To[int32](11),
   351  								CachingType: "",
   352  							},
   353  							DataDisks:    []DataDisk{},
   354  							SSHPublicKey: "",
   355  						},
   356  					},
   357  				},
   358  				ObjectMeta: metav1.ObjectMeta{
   359  					Name: "OldTemplate",
   360  				},
   361  			},
   362  			template: &AzureMachineTemplate{
   363  				Spec: AzureMachineTemplateSpec{
   364  					Template: AzureMachineTemplateResource{
   365  						Spec: AzureMachineSpec{
   366  							VMSize:        "size",
   367  							FailureDomain: &failureDomain,
   368  							OSDisk: OSDisk{
   369  								OSType:      "type",
   370  								DiskSizeGB:  ptr.To[int32](11),
   371  								CachingType: "None",
   372  							},
   373  							DataDisks:    []DataDisk{},
   374  							SSHPublicKey: "fake ssh key",
   375  							NetworkInterfaces: []NetworkInterface{{
   376  								PrivateIPConfigs: 1,
   377  							}},
   378  						},
   379  					},
   380  				},
   381  				ObjectMeta: metav1.ObjectMeta{
   382  					Name: "NewTemplate",
   383  				},
   384  			},
   385  			wantErr: false,
   386  		},
   387  		{
   388  			name: "AzureMachineTemplate ssh key removed",
   389  			oldTemplate: &AzureMachineTemplate{
   390  				Spec: AzureMachineTemplateSpec{
   391  					Template: AzureMachineTemplateResource{
   392  						Spec: AzureMachineSpec{
   393  							VMSize:        "size",
   394  							FailureDomain: &failureDomain,
   395  							OSDisk: OSDisk{
   396  								OSType:      "type",
   397  								DiskSizeGB:  ptr.To[int32](11),
   398  								CachingType: "None",
   399  							},
   400  							DataDisks:    []DataDisk{},
   401  							SSHPublicKey: "some key",
   402  						},
   403  					},
   404  				},
   405  				ObjectMeta: metav1.ObjectMeta{
   406  					Name: "OldTemplate",
   407  				},
   408  			},
   409  			template: &AzureMachineTemplate{
   410  				Spec: AzureMachineTemplateSpec{
   411  					Template: AzureMachineTemplateResource{
   412  						Spec: AzureMachineSpec{
   413  							VMSize:        "size",
   414  							FailureDomain: &failureDomain,
   415  							OSDisk: OSDisk{
   416  								OSType:      "type",
   417  								DiskSizeGB:  ptr.To[int32](11),
   418  								CachingType: "None",
   419  							},
   420  							DataDisks:    []DataDisk{},
   421  							SSHPublicKey: "",
   422  						},
   423  					},
   424  				},
   425  				ObjectMeta: metav1.ObjectMeta{
   426  					Name: "NewTemplate",
   427  				},
   428  			},
   429  			wantErr: true,
   430  		},
   431  		{
   432  			name: "AzureMachineTemplate with legacy subnetName updated to new networkInterfaces",
   433  			oldTemplate: &AzureMachineTemplate{
   434  				Spec: AzureMachineTemplateSpec{
   435  					Template: AzureMachineTemplateResource{
   436  						Spec: AzureMachineSpec{
   437  							VMSize:        "size",
   438  							FailureDomain: &failureDomain,
   439  							OSDisk: OSDisk{
   440  								OSType:      "type",
   441  								DiskSizeGB:  ptr.To[int32](11),
   442  								CachingType: "None",
   443  							},
   444  							DataDisks:             []DataDisk{},
   445  							SSHPublicKey:          "fake ssh key",
   446  							SubnetName:            "subnet1",
   447  							AcceleratedNetworking: ptr.To(true),
   448  						},
   449  					},
   450  				},
   451  			},
   452  			template: &AzureMachineTemplate{
   453  				Spec: AzureMachineTemplateSpec{
   454  					Template: AzureMachineTemplateResource{
   455  						Spec: AzureMachineSpec{
   456  							VMSize:        "size",
   457  							FailureDomain: &failureDomain,
   458  							OSDisk: OSDisk{
   459  								OSType:      "type",
   460  								DiskSizeGB:  ptr.To[int32](11),
   461  								CachingType: "None",
   462  							},
   463  							DataDisks:             []DataDisk{},
   464  							SSHPublicKey:          "fake ssh key",
   465  							SubnetName:            "",
   466  							AcceleratedNetworking: nil,
   467  							NetworkInterfaces: []NetworkInterface{
   468  								{
   469  									SubnetName:            "subnet1",
   470  									AcceleratedNetworking: ptr.To(true),
   471  									PrivateIPConfigs:      1,
   472  								},
   473  							},
   474  						},
   475  					},
   476  				},
   477  			},
   478  			wantErr: false,
   479  		},
   480  		{
   481  			name: "AzureMachineTemplate with legacy AcceleratedNetworking updated to new networkInterfaces",
   482  			oldTemplate: &AzureMachineTemplate{
   483  				Spec: AzureMachineTemplateSpec{
   484  					Template: AzureMachineTemplateResource{
   485  						Spec: AzureMachineSpec{
   486  							VMSize:        "size",
   487  							FailureDomain: &failureDomain,
   488  							OSDisk: OSDisk{
   489  								OSType:      "type",
   490  								DiskSizeGB:  ptr.To[int32](11),
   491  								CachingType: "None",
   492  							},
   493  							DataDisks:             []DataDisk{},
   494  							SSHPublicKey:          "fake ssh key",
   495  							SubnetName:            "",
   496  							AcceleratedNetworking: ptr.To(true),
   497  							NetworkInterfaces:     []NetworkInterface{},
   498  						},
   499  					},
   500  				},
   501  			},
   502  			template: &AzureMachineTemplate{
   503  				Spec: AzureMachineTemplateSpec{
   504  					Template: AzureMachineTemplateResource{
   505  						Spec: AzureMachineSpec{
   506  							VMSize:        "size",
   507  							FailureDomain: &failureDomain,
   508  							OSDisk: OSDisk{
   509  								OSType:      "type",
   510  								DiskSizeGB:  ptr.To[int32](11),
   511  								CachingType: "None",
   512  							},
   513  							DataDisks:             []DataDisk{},
   514  							SSHPublicKey:          "fake ssh key",
   515  							SubnetName:            "",
   516  							AcceleratedNetworking: nil,
   517  							NetworkInterfaces: []NetworkInterface{
   518  								{
   519  									SubnetName:            "",
   520  									AcceleratedNetworking: ptr.To(true),
   521  									PrivateIPConfigs:      1,
   522  								},
   523  							},
   524  						},
   525  					},
   526  				},
   527  			},
   528  			wantErr: false,
   529  		},
   530  		{
   531  			name: "AzureMachineTemplate with modified networkInterfaces is immutable",
   532  			oldTemplate: &AzureMachineTemplate{
   533  				Spec: AzureMachineTemplateSpec{
   534  					Template: AzureMachineTemplateResource{
   535  						Spec: AzureMachineSpec{
   536  							VMSize:        "size",
   537  							FailureDomain: &failureDomain,
   538  							OSDisk: OSDisk{
   539  								OSType:      "type",
   540  								DiskSizeGB:  ptr.To[int32](11),
   541  								CachingType: "None",
   542  							},
   543  							DataDisks:    []DataDisk{},
   544  							SSHPublicKey: "fake ssh key",
   545  							NetworkInterfaces: []NetworkInterface{
   546  								{
   547  									SubnetName:            "subnet1",
   548  									AcceleratedNetworking: ptr.To(true),
   549  									PrivateIPConfigs:      1,
   550  								},
   551  							},
   552  						},
   553  					},
   554  				},
   555  			},
   556  			template: &AzureMachineTemplate{
   557  				Spec: AzureMachineTemplateSpec{
   558  					Template: AzureMachineTemplateResource{
   559  						Spec: AzureMachineSpec{
   560  							VMSize:        "size",
   561  							FailureDomain: &failureDomain,
   562  							OSDisk: OSDisk{
   563  								OSType:      "type",
   564  								DiskSizeGB:  ptr.To[int32](11),
   565  								CachingType: "None",
   566  							},
   567  							DataDisks:    []DataDisk{},
   568  							SSHPublicKey: "fake ssh key",
   569  							NetworkInterfaces: []NetworkInterface{
   570  								{
   571  									SubnetName:            "subnet2",
   572  									AcceleratedNetworking: ptr.To(true),
   573  									PrivateIPConfigs:      1,
   574  								},
   575  							},
   576  						},
   577  					},
   578  				},
   579  			},
   580  			wantErr: true,
   581  		},
   582  	}
   583  
   584  	// dry-run=true
   585  	for _, amt := range tests {
   586  		amt := amt
   587  		t.Run(amt.name, func(t *testing.T) {
   588  			g := NewWithT(t)
   589  			ctx := admission.NewContextWithRequest(context.Background(), admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(true)}})
   590  			_, err := amt.template.ValidateUpdate(ctx, amt.oldTemplate, amt.template)
   591  			if amt.wantErr {
   592  				g.Expect(err).To(HaveOccurred())
   593  			} else {
   594  				g.Expect(err).NotTo(HaveOccurred())
   595  			}
   596  		})
   597  	}
   598  	// dry-run=false
   599  	for _, amt := range tests {
   600  		amt := amt
   601  		t.Run(amt.name, func(t *testing.T) {
   602  			t.Parallel()
   603  			g := NewWithT(t)
   604  			ctx := admission.NewContextWithRequest(context.Background(), admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}})
   605  			_, err := amt.template.ValidateUpdate(ctx, amt.oldTemplate, amt.template)
   606  			if amt.wantErr {
   607  				g.Expect(err).To(HaveOccurred())
   608  			} else {
   609  				g.Expect(err).NotTo(HaveOccurred())
   610  			}
   611  		})
   612  	}
   613  }
   614  
   615  func createAzureMachineTemplateFromMachine(machine *AzureMachine) *AzureMachineTemplate {
   616  	return &AzureMachineTemplate{
   617  		Spec: AzureMachineTemplateSpec{
   618  			Template: AzureMachineTemplateResource{
   619  				Spec: machine.Spec,
   620  			},
   621  		},
   622  	}
   623  }