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

     1  /*
     2  Copyright 2023 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  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    24  	. "github.com/onsi/gomega"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	utilfeature "k8s.io/component-base/featuregate/testing"
    28  	"k8s.io/utils/ptr"
    29  	"sigs.k8s.io/cluster-api-provider-azure/feature"
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  	clusterctlv1alpha3 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    32  	capifeature "sigs.k8s.io/cluster-api/feature"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    35  )
    36  
    37  func TestAzureManagedMachinePoolDefaultingWebhook(t *testing.T) {
    38  	g := NewWithT(t)
    39  
    40  	t.Logf("Testing ammp defaulting webhook with mode system")
    41  	ammp := &AzureManagedMachinePool{
    42  		ObjectMeta: metav1.ObjectMeta{
    43  			Name: "fooname",
    44  		},
    45  		Spec: AzureManagedMachinePoolSpec{
    46  			AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
    47  				Mode:         "System",
    48  				SKU:          "StandardD2S_V3",
    49  				OSDiskSizeGB: ptr.To(512),
    50  			},
    51  		},
    52  	}
    53  	var client client.Client
    54  	mw := &azureManagedMachinePoolWebhook{
    55  		Client: client,
    56  	}
    57  	err := mw.Default(context.Background(), ammp)
    58  	g.Expect(err).NotTo(HaveOccurred())
    59  	g.Expect(ammp.Labels).NotTo(BeNil())
    60  	val, ok := ammp.Labels[LabelAgentPoolMode]
    61  	g.Expect(ok).To(BeTrue())
    62  	g.Expect(val).To(Equal("System"))
    63  	g.Expect(*ammp.Spec.Name).To(Equal("fooname"))
    64  	g.Expect(*ammp.Spec.OSType).To(Equal(LinuxOS))
    65  
    66  	t.Logf("Testing ammp defaulting webhook with empty string name specified in Spec")
    67  	emptyName := ""
    68  	ammp.Spec.Name = &emptyName
    69  	err = mw.Default(context.Background(), ammp)
    70  	g.Expect(err).NotTo(HaveOccurred())
    71  	g.Expect(*ammp.Spec.Name).To(Equal("fooname"))
    72  
    73  	t.Logf("Testing ammp defaulting webhook with normal name specified in Spec")
    74  	normalName := "barname"
    75  	ammp.Spec.Name = &normalName
    76  	err = mw.Default(context.Background(), ammp)
    77  	g.Expect(err).NotTo(HaveOccurred())
    78  	g.Expect(*ammp.Spec.Name).To(Equal("barname"))
    79  
    80  	t.Logf("Testing ammp defaulting webhook with normal OsDiskType specified in Spec")
    81  	normalOsDiskType := "Ephemeral"
    82  	ammp.Spec.OsDiskType = &normalOsDiskType
    83  	err = mw.Default(context.Background(), ammp)
    84  	g.Expect(err).NotTo(HaveOccurred())
    85  	g.Expect(*ammp.Spec.OsDiskType).To(Equal("Ephemeral"))
    86  }
    87  
    88  func TestAzureManagedMachinePoolUpdatingWebhook(t *testing.T) {
    89  	t.Logf("Testing ammp updating webhook with mode system")
    90  
    91  	tests := []struct {
    92  		name    string
    93  		new     *AzureManagedMachinePool
    94  		old     *AzureManagedMachinePool
    95  		wantErr bool
    96  	}{
    97  		{
    98  			name: "Cannot change Name of the agentpool",
    99  			new: &AzureManagedMachinePool{
   100  				Spec: AzureManagedMachinePoolSpec{
   101  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   102  						Name: ptr.To("pool-new"),
   103  					},
   104  				},
   105  			},
   106  			old: &AzureManagedMachinePool{
   107  				Spec: AzureManagedMachinePoolSpec{
   108  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   109  						Name: ptr.To("pool-old"),
   110  					},
   111  				},
   112  			},
   113  			wantErr: true,
   114  		},
   115  		{
   116  			name: "Cannot change SKU of the agentpool",
   117  			new: &AzureManagedMachinePool{
   118  				Spec: AzureManagedMachinePoolSpec{
   119  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   120  						Mode:         "System",
   121  						SKU:          "StandardD2S_V3",
   122  						OSDiskSizeGB: ptr.To(512),
   123  					},
   124  				},
   125  			},
   126  			old: &AzureManagedMachinePool{
   127  				Spec: AzureManagedMachinePoolSpec{
   128  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   129  						Mode:         "System",
   130  						SKU:          "StandardD2S_V4",
   131  						OSDiskSizeGB: ptr.To(512),
   132  					},
   133  				},
   134  			},
   135  			wantErr: true,
   136  		},
   137  		{
   138  			name: "Cannot change OSType of the agentpool",
   139  			new: &AzureManagedMachinePool{
   140  				Spec: AzureManagedMachinePoolSpec{
   141  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   142  						OSType:       ptr.To(LinuxOS),
   143  						Mode:         "System",
   144  						SKU:          "StandardD2S_V3",
   145  						OSDiskSizeGB: ptr.To(512),
   146  					},
   147  				},
   148  			},
   149  			old: &AzureManagedMachinePool{
   150  				Spec: AzureManagedMachinePoolSpec{
   151  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   152  						OSType:       ptr.To(WindowsOS),
   153  						Mode:         "System",
   154  						SKU:          "StandardD2S_V4",
   155  						OSDiskSizeGB: ptr.To(512),
   156  					},
   157  				},
   158  			},
   159  			wantErr: true,
   160  		},
   161  		{
   162  			name: "Cannot change OSDiskSizeGB of the agentpool",
   163  			new: &AzureManagedMachinePool{
   164  				Spec: AzureManagedMachinePoolSpec{
   165  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   166  						Mode:         "System",
   167  						SKU:          "StandardD2S_V3",
   168  						OSDiskSizeGB: ptr.To(512),
   169  					},
   170  				},
   171  			},
   172  			old: &AzureManagedMachinePool{
   173  				Spec: AzureManagedMachinePoolSpec{
   174  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   175  						Mode:         "System",
   176  						SKU:          "StandardD2S_V3",
   177  						OSDiskSizeGB: ptr.To(1024),
   178  					},
   179  				},
   180  			},
   181  			wantErr: true,
   182  		},
   183  		{
   184  			name: "Cannot add AvailabilityZones after creating agentpool",
   185  			new: &AzureManagedMachinePool{
   186  				Spec: AzureManagedMachinePoolSpec{
   187  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   188  						Mode:              "System",
   189  						SKU:               "StandardD2S_V3",
   190  						OSDiskSizeGB:      ptr.To(512),
   191  						AvailabilityZones: []string{"1", "2", "3"},
   192  					},
   193  				},
   194  			},
   195  			old: &AzureManagedMachinePool{
   196  				Spec: AzureManagedMachinePoolSpec{
   197  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   198  						Mode:         "System",
   199  						SKU:          "StandardD2S_V3",
   200  						OSDiskSizeGB: ptr.To(512),
   201  					},
   202  				},
   203  			},
   204  			wantErr: true,
   205  		},
   206  		{
   207  			name: "Cannot remove AvailabilityZones after creating agentpool",
   208  			new: &AzureManagedMachinePool{
   209  				Spec: AzureManagedMachinePoolSpec{
   210  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   211  						Mode:         "System",
   212  						SKU:          "StandardD2S_V3",
   213  						OSDiskSizeGB: ptr.To(512),
   214  					},
   215  				},
   216  			},
   217  			old: &AzureManagedMachinePool{
   218  				Spec: AzureManagedMachinePoolSpec{
   219  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   220  						Mode:              "System",
   221  						SKU:               "StandardD2S_V3",
   222  						OSDiskSizeGB:      ptr.To(512),
   223  						AvailabilityZones: []string{"1", "2", "3"},
   224  					},
   225  				},
   226  			},
   227  			wantErr: true,
   228  		},
   229  		{
   230  			name: "Cannot change AvailabilityZones of the agentpool",
   231  			new: &AzureManagedMachinePool{
   232  				Spec: AzureManagedMachinePoolSpec{
   233  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   234  						Mode:              "System",
   235  						SKU:               "StandardD2S_V3",
   236  						OSDiskSizeGB:      ptr.To(512),
   237  						AvailabilityZones: []string{"1", "2"},
   238  					},
   239  				},
   240  			},
   241  			old: &AzureManagedMachinePool{
   242  				Spec: AzureManagedMachinePoolSpec{
   243  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   244  						Mode:              "System",
   245  						SKU:               "StandardD2S_V3",
   246  						OSDiskSizeGB:      ptr.To(512),
   247  						AvailabilityZones: []string{"1", "2", "3"},
   248  					},
   249  				},
   250  			},
   251  			wantErr: true,
   252  		},
   253  		{
   254  			name: "AvailabilityZones order can be different",
   255  			new: &AzureManagedMachinePool{
   256  				Spec: AzureManagedMachinePoolSpec{
   257  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   258  						Mode:              "System",
   259  						SKU:               "StandardD2S_V3",
   260  						OSDiskSizeGB:      ptr.To(512),
   261  						AvailabilityZones: []string{"1", "3", "2"},
   262  					},
   263  				},
   264  			},
   265  			old: &AzureManagedMachinePool{
   266  				Spec: AzureManagedMachinePoolSpec{
   267  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   268  						Mode:              "System",
   269  						SKU:               "StandardD2S_V3",
   270  						OSDiskSizeGB:      ptr.To(512),
   271  						AvailabilityZones: []string{"1", "2", "3"},
   272  					},
   273  				},
   274  			},
   275  			wantErr: false,
   276  		},
   277  		{
   278  			name: "Cannot change MaxPods of the agentpool",
   279  			new: &AzureManagedMachinePool{
   280  				Spec: AzureManagedMachinePoolSpec{
   281  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   282  						Mode:         "System",
   283  						SKU:          "StandardD2S_V3",
   284  						OSDiskSizeGB: ptr.To(512),
   285  						MaxPods:      ptr.To(24),
   286  					},
   287  				},
   288  			},
   289  			old: &AzureManagedMachinePool{
   290  				Spec: AzureManagedMachinePoolSpec{
   291  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   292  						Mode:         "System",
   293  						SKU:          "StandardD2S_V3",
   294  						OSDiskSizeGB: ptr.To(512),
   295  						MaxPods:      ptr.To(25),
   296  					},
   297  				},
   298  			},
   299  			wantErr: true,
   300  		},
   301  		{
   302  			name: "Unchanged MaxPods in an agentpool should not result in an error",
   303  			new: &AzureManagedMachinePool{
   304  				Spec: AzureManagedMachinePoolSpec{
   305  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   306  						Mode:         "System",
   307  						SKU:          "StandardD2S_V3",
   308  						OSDiskSizeGB: ptr.To(512),
   309  						MaxPods:      ptr.To(30),
   310  					},
   311  				},
   312  			},
   313  			old: &AzureManagedMachinePool{
   314  				Spec: AzureManagedMachinePoolSpec{
   315  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   316  						Mode:         "System",
   317  						SKU:          "StandardD2S_V3",
   318  						OSDiskSizeGB: ptr.To(512),
   319  						MaxPods:      ptr.To(30),
   320  					},
   321  				},
   322  			},
   323  			wantErr: false,
   324  		},
   325  		{
   326  			name: "Cannot change OSDiskType of the agentpool",
   327  			new: &AzureManagedMachinePool{
   328  				Spec: AzureManagedMachinePoolSpec{
   329  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   330  						Mode:         "System",
   331  						SKU:          "StandardD2S_V3",
   332  						OSDiskSizeGB: ptr.To(512),
   333  						MaxPods:      ptr.To(24),
   334  						OsDiskType:   ptr.To(string(asocontainerservicev1.OSDiskType_Ephemeral)),
   335  					},
   336  				},
   337  			},
   338  			old: &AzureManagedMachinePool{
   339  				Spec: AzureManagedMachinePoolSpec{
   340  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   341  						Mode:         "System",
   342  						SKU:          "StandardD2S_V3",
   343  						OSDiskSizeGB: ptr.To(512),
   344  						MaxPods:      ptr.To(24),
   345  						OsDiskType:   ptr.To(string(asocontainerservicev1.OSDiskType_Managed)),
   346  					},
   347  				},
   348  			},
   349  			wantErr: true,
   350  		},
   351  		{
   352  			name: "Unchanged OSDiskType in an agentpool should not result in an error",
   353  			new: &AzureManagedMachinePool{
   354  				Spec: AzureManagedMachinePoolSpec{
   355  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   356  						Mode:         "System",
   357  						SKU:          "StandardD2S_V3",
   358  						OSDiskSizeGB: ptr.To(512),
   359  						MaxPods:      ptr.To(30),
   360  						OsDiskType:   ptr.To(string(asocontainerservicev1.OSDiskType_Managed)),
   361  					},
   362  				},
   363  			},
   364  			old: &AzureManagedMachinePool{
   365  				Spec: AzureManagedMachinePoolSpec{
   366  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   367  						Mode:         "System",
   368  						SKU:          "StandardD2S_V3",
   369  						OSDiskSizeGB: ptr.To(512),
   370  						MaxPods:      ptr.To(30),
   371  						OsDiskType:   ptr.To(string(asocontainerservicev1.OSDiskType_Managed)),
   372  					},
   373  				},
   374  			},
   375  			wantErr: false,
   376  		},
   377  		{
   378  			name: "Unexpected error, value EnableUltraSSD is unchanged",
   379  			new: &AzureManagedMachinePool{
   380  				Spec: AzureManagedMachinePoolSpec{
   381  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   382  						EnableUltraSSD: ptr.To(true),
   383  					},
   384  				},
   385  			},
   386  			old: &AzureManagedMachinePool{
   387  				Spec: AzureManagedMachinePoolSpec{
   388  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   389  						EnableUltraSSD: ptr.To(true),
   390  					},
   391  				},
   392  			},
   393  			wantErr: false,
   394  		},
   395  		{
   396  			name: "EnableUltraSSD feature is immutable and currently enabled on this agentpool",
   397  			new: &AzureManagedMachinePool{
   398  				Spec: AzureManagedMachinePoolSpec{
   399  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   400  						EnableUltraSSD: ptr.To(false),
   401  					},
   402  				},
   403  			},
   404  			old: &AzureManagedMachinePool{
   405  				Spec: AzureManagedMachinePoolSpec{
   406  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   407  						EnableUltraSSD: ptr.To(true),
   408  					},
   409  				},
   410  			},
   411  			wantErr: true,
   412  		},
   413  		{
   414  			name: "Unexpected error, value EnableNodePublicIP is unchanged",
   415  			new: &AzureManagedMachinePool{
   416  				Spec: AzureManagedMachinePoolSpec{
   417  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   418  						EnableNodePublicIP: ptr.To(true),
   419  					},
   420  				},
   421  			},
   422  			old: &AzureManagedMachinePool{
   423  				Spec: AzureManagedMachinePoolSpec{
   424  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   425  						EnableNodePublicIP: ptr.To(true),
   426  					},
   427  				},
   428  			},
   429  			wantErr: false,
   430  		},
   431  		{
   432  			name: "EnableNodePublicIP feature is immutable and currently enabled on this agentpool",
   433  			new: &AzureManagedMachinePool{
   434  				Spec: AzureManagedMachinePoolSpec{
   435  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   436  						EnableNodePublicIP: ptr.To(false),
   437  					},
   438  				},
   439  			},
   440  			old: &AzureManagedMachinePool{
   441  				Spec: AzureManagedMachinePoolSpec{
   442  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   443  						EnableNodePublicIP: ptr.To(true),
   444  					},
   445  				},
   446  			},
   447  			wantErr: true,
   448  		},
   449  		{
   450  			name: "NodeTaints are mutable",
   451  			new: &AzureManagedMachinePool{
   452  				Spec: AzureManagedMachinePoolSpec{
   453  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   454  						Taints: []Taint{
   455  							{
   456  								Effect: TaintEffect("NoSchedule"),
   457  								Key:    "foo",
   458  								Value:  "baz",
   459  							},
   460  						},
   461  					},
   462  				},
   463  			},
   464  			old: &AzureManagedMachinePool{
   465  				Spec: AzureManagedMachinePoolSpec{
   466  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   467  						Taints: []Taint{
   468  							{
   469  								Effect: TaintEffect("NoSchedule"),
   470  								Key:    "foo",
   471  								Value:  "bar",
   472  							},
   473  						},
   474  					},
   475  				},
   476  			},
   477  			wantErr: false,
   478  		},
   479  		{
   480  			name: "Can't add a node label that begins with kubernetes.azure.com",
   481  			new: &AzureManagedMachinePool{
   482  				Spec: AzureManagedMachinePoolSpec{
   483  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   484  						NodeLabels: map[string]string{
   485  							"foo":                                   "bar",
   486  							"kubernetes.azure.com/scalesetpriority": "spot",
   487  						},
   488  					},
   489  				},
   490  			},
   491  			old: &AzureManagedMachinePool{
   492  				Spec: AzureManagedMachinePoolSpec{
   493  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   494  						NodeLabels: map[string]string{
   495  							"foo": "bar",
   496  						},
   497  					},
   498  				},
   499  			},
   500  			wantErr: true,
   501  		},
   502  		{
   503  			name: "Can't update kubeletconfig",
   504  			new: &AzureManagedMachinePool{
   505  				Spec: AzureManagedMachinePoolSpec{
   506  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   507  						KubeletConfig: &KubeletConfig{
   508  							CPUCfsQuota: ptr.To(true),
   509  						},
   510  					},
   511  				},
   512  			},
   513  			old: &AzureManagedMachinePool{
   514  				Spec: AzureManagedMachinePoolSpec{
   515  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   516  						KubeletConfig: &KubeletConfig{
   517  							CPUCfsQuota: ptr.To(false),
   518  						},
   519  					},
   520  				},
   521  			},
   522  			wantErr: true,
   523  		},
   524  		{
   525  			name: "Can't update LinuxOSConfig",
   526  			new: &AzureManagedMachinePool{
   527  				Spec: AzureManagedMachinePoolSpec{
   528  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   529  						LinuxOSConfig: &LinuxOSConfig{
   530  							SwapFileSizeMB: ptr.To(10),
   531  						},
   532  					},
   533  				},
   534  			},
   535  			old: &AzureManagedMachinePool{
   536  				Spec: AzureManagedMachinePoolSpec{
   537  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   538  						LinuxOSConfig: &LinuxOSConfig{
   539  							SwapFileSizeMB: ptr.To(5),
   540  						},
   541  					},
   542  				},
   543  			},
   544  			wantErr: true,
   545  		},
   546  		{
   547  			name: "Can't update SubnetName with error",
   548  			new: &AzureManagedMachinePool{
   549  				Spec: AzureManagedMachinePoolSpec{
   550  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   551  						SubnetName: ptr.To("my-subnet"),
   552  					},
   553  				},
   554  			},
   555  			old: &AzureManagedMachinePool{
   556  				Spec: AzureManagedMachinePoolSpec{
   557  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   558  						SubnetName: ptr.To("my-subnet-1"),
   559  					},
   560  				},
   561  			},
   562  			wantErr: true,
   563  		},
   564  		{
   565  			name: "Can update SubnetName if subnetName is empty",
   566  			new: &AzureManagedMachinePool{
   567  				Spec: AzureManagedMachinePoolSpec{
   568  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   569  						SubnetName: ptr.To("my-subnet"),
   570  					},
   571  				},
   572  			},
   573  			old: &AzureManagedMachinePool{
   574  				Spec: AzureManagedMachinePoolSpec{
   575  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   576  						SubnetName: nil,
   577  					},
   578  				},
   579  			},
   580  			wantErr: false,
   581  		},
   582  		{
   583  			name: "Can't update SubnetName without error",
   584  			new: &AzureManagedMachinePool{
   585  				Spec: AzureManagedMachinePoolSpec{
   586  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   587  						SubnetName: ptr.To("my-subnet"),
   588  					},
   589  				},
   590  			},
   591  			old: &AzureManagedMachinePool{
   592  				Spec: AzureManagedMachinePoolSpec{
   593  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   594  						SubnetName: ptr.To("my-subnet"),
   595  					},
   596  				},
   597  			},
   598  			wantErr: false,
   599  		},
   600  		{
   601  			name: "Cannot update enableFIPS",
   602  			new: &AzureManagedMachinePool{
   603  				Spec: AzureManagedMachinePoolSpec{
   604  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   605  						EnableFIPS: ptr.To(true),
   606  					},
   607  				},
   608  			},
   609  			old: &AzureManagedMachinePool{
   610  				Spec: AzureManagedMachinePoolSpec{
   611  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   612  						EnableFIPS: ptr.To(false),
   613  					},
   614  				},
   615  			},
   616  			wantErr: true,
   617  		},
   618  		{
   619  			name: "Cannot update enableEncryptionAtHost",
   620  			new: &AzureManagedMachinePool{
   621  				Spec: AzureManagedMachinePoolSpec{
   622  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   623  						EnableEncryptionAtHost: ptr.To(true),
   624  					},
   625  				},
   626  			},
   627  			old: &AzureManagedMachinePool{
   628  				Spec: AzureManagedMachinePoolSpec{
   629  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   630  						EnableEncryptionAtHost: ptr.To(false),
   631  					},
   632  				},
   633  			},
   634  			wantErr: true,
   635  		},
   636  	}
   637  	var client client.Client
   638  	for _, tc := range tests {
   639  		tc := tc
   640  		t.Run(tc.name, func(t *testing.T) {
   641  			t.Parallel()
   642  			g := NewWithT(t)
   643  			mw := &azureManagedMachinePoolWebhook{
   644  				Client: client,
   645  			}
   646  			_, err := mw.ValidateUpdate(context.Background(), tc.old, tc.new)
   647  			if tc.wantErr {
   648  				g.Expect(err).To(HaveOccurred())
   649  			} else {
   650  				g.Expect(err).NotTo(HaveOccurred())
   651  			}
   652  		})
   653  	}
   654  }
   655  
   656  func TestAzureManagedMachinePool_ValidateCreate(t *testing.T) {
   657  	tests := []struct {
   658  		name     string
   659  		ammp     *AzureManagedMachinePool
   660  		wantErr  bool
   661  		errorLen int
   662  	}{
   663  		{
   664  			name:    "valid",
   665  			ammp:    getKnownValidAzureManagedMachinePool(),
   666  			wantErr: false,
   667  		},
   668  		{
   669  			name: "another valid permutation",
   670  			ammp: &AzureManagedMachinePool{
   671  				Spec: AzureManagedMachinePoolSpec{
   672  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   673  						MaxPods:    ptr.To(249),
   674  						OsDiskType: ptr.To(string(asocontainerservicev1.OSDiskType_Managed)),
   675  					},
   676  				},
   677  			},
   678  			wantErr: false,
   679  		},
   680  		{
   681  			name: "valid - optional configuration not present",
   682  			ammp: &AzureManagedMachinePool{
   683  				Spec: AzureManagedMachinePoolSpec{},
   684  			},
   685  			wantErr: false,
   686  		},
   687  		{
   688  			name: "too many MaxPods",
   689  			ammp: &AzureManagedMachinePool{
   690  				Spec: AzureManagedMachinePoolSpec{
   691  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   692  						MaxPods: ptr.To(251),
   693  					},
   694  				},
   695  			},
   696  			wantErr:  true,
   697  			errorLen: 1,
   698  		},
   699  		{
   700  			name: "invalid subnetname",
   701  			ammp: &AzureManagedMachinePool{
   702  				Spec: AzureManagedMachinePoolSpec{
   703  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   704  						SubnetName: ptr.To("1+subnet"),
   705  					},
   706  				},
   707  			},
   708  			wantErr:  true,
   709  			errorLen: 1,
   710  		},
   711  		{
   712  			name: "invalid subnetname",
   713  			ammp: &AzureManagedMachinePool{
   714  				Spec: AzureManagedMachinePoolSpec{
   715  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   716  						SubnetName: ptr.To("1"),
   717  					},
   718  				},
   719  			},
   720  			wantErr:  true,
   721  			errorLen: 1,
   722  		},
   723  		{
   724  			name: "invalid subnetname",
   725  			ammp: &AzureManagedMachinePool{
   726  				Spec: AzureManagedMachinePoolSpec{
   727  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   728  						SubnetName: ptr.To("-a_b-c"),
   729  					},
   730  				},
   731  			},
   732  			wantErr:  true,
   733  			errorLen: 1,
   734  		},
   735  		{
   736  			name: "invalid subnetname with versioning",
   737  			ammp: &AzureManagedMachinePool{
   738  				Spec: AzureManagedMachinePoolSpec{
   739  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   740  						SubnetName: ptr.To("workload-ampt-v0.1.0."),
   741  					},
   742  				},
   743  			},
   744  			wantErr:  true,
   745  			errorLen: 1,
   746  		},
   747  		{
   748  			name: "invalid subnetname",
   749  			ammp: &AzureManagedMachinePool{
   750  				Spec: AzureManagedMachinePoolSpec{
   751  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   752  						SubnetName: ptr.To("-_-_"),
   753  					},
   754  				},
   755  			},
   756  			wantErr:  true,
   757  			errorLen: 1,
   758  		},
   759  		{
   760  			name: "invalid subnetname",
   761  			ammp: &AzureManagedMachinePool{
   762  				Spec: AzureManagedMachinePoolSpec{
   763  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   764  						SubnetName: ptr.To("abc@#$"),
   765  					},
   766  				},
   767  			},
   768  			wantErr:  true,
   769  			errorLen: 1,
   770  		},
   771  		{
   772  			name: "invalid subnetname with character length 81",
   773  			ammp: &AzureManagedMachinePool{
   774  				Spec: AzureManagedMachinePoolSpec{
   775  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   776  						SubnetName: ptr.To("3DgIb8EZMkLs0KlyPaTcNxoJU9ufmW6jvXrweqz1hVp5nS4RtH2QY7AFOiC5nS4RtH2QY7AFOiC3DgIb8"),
   777  					},
   778  				},
   779  			},
   780  			wantErr:  true,
   781  			errorLen: 1,
   782  		},
   783  		{
   784  			name: "valid subnetname with character length 80",
   785  			ammp: &AzureManagedMachinePool{
   786  				Spec: AzureManagedMachinePoolSpec{
   787  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   788  						SubnetName: ptr.To("3DgIb8EZMkLs0KlyPaTcNxoJU9ufmW6jvXrweqz1hVp5nS4RtH2QY7AFOiC5nS4RtH2QY7AFOiC3DgIb"),
   789  					},
   790  				},
   791  			},
   792  			wantErr: false,
   793  		},
   794  		{
   795  			name: "valid subnetname with versioning",
   796  			ammp: &AzureManagedMachinePool{
   797  				Spec: AzureManagedMachinePoolSpec{
   798  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   799  						SubnetName: ptr.To("workload-ampt-v0.1.0"),
   800  					},
   801  				},
   802  			},
   803  			wantErr: false,
   804  		},
   805  		{
   806  			name: "valid subnetname",
   807  			ammp: &AzureManagedMachinePool{
   808  				Spec: AzureManagedMachinePoolSpec{
   809  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   810  						SubnetName: ptr.To("1abc"),
   811  					},
   812  				},
   813  			},
   814  			wantErr: false,
   815  		},
   816  		{
   817  			name: "valid subnetname",
   818  			ammp: &AzureManagedMachinePool{
   819  				Spec: AzureManagedMachinePoolSpec{
   820  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   821  						SubnetName: ptr.To("1-a-b-c"),
   822  					},
   823  				},
   824  			},
   825  			wantErr: false,
   826  		},
   827  		{
   828  			name: "valid subnetname",
   829  			ammp: &AzureManagedMachinePool{
   830  				Spec: AzureManagedMachinePoolSpec{
   831  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   832  						SubnetName: ptr.To("my-subnet"),
   833  					},
   834  				},
   835  			},
   836  			wantErr: false,
   837  		},
   838  		{
   839  			name: "too few MaxPods",
   840  			ammp: &AzureManagedMachinePool{
   841  				Spec: AzureManagedMachinePoolSpec{
   842  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   843  						MaxPods: ptr.To(9),
   844  					},
   845  				},
   846  			},
   847  			wantErr:  true,
   848  			errorLen: 1,
   849  		},
   850  		{
   851  			name: "ostype Windows with System mode not allowed",
   852  			ammp: &AzureManagedMachinePool{
   853  				Spec: AzureManagedMachinePoolSpec{
   854  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   855  						Mode:   "System",
   856  						OSType: ptr.To(WindowsOS),
   857  					},
   858  				},
   859  			},
   860  			wantErr:  true,
   861  			errorLen: 1,
   862  		},
   863  		{
   864  			name: "ostype windows with User mode",
   865  			ammp: &AzureManagedMachinePool{
   866  				Spec: AzureManagedMachinePoolSpec{
   867  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   868  						Mode:   "User",
   869  						OSType: ptr.To(WindowsOS),
   870  					},
   871  				},
   872  			},
   873  			wantErr: false,
   874  		},
   875  		{
   876  			name: "Windows clusters with 6char or less name",
   877  			ammp: &AzureManagedMachinePool{
   878  				ObjectMeta: metav1.ObjectMeta{
   879  					Name: "pool0",
   880  				},
   881  				Spec: AzureManagedMachinePoolSpec{
   882  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   883  						Mode:   "User",
   884  						OSType: ptr.To(WindowsOS),
   885  					},
   886  				},
   887  			},
   888  			wantErr: false,
   889  		},
   890  		{
   891  			name: "Windows clusters with more than 6char names are not allowed",
   892  			ammp: &AzureManagedMachinePool{
   893  				Spec: AzureManagedMachinePoolSpec{
   894  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   895  						Name:   ptr.To("pool0-name-too-long"),
   896  						Mode:   "User",
   897  						OSType: ptr.To(WindowsOS),
   898  					},
   899  				},
   900  			},
   901  			wantErr:  true,
   902  			errorLen: 1,
   903  		},
   904  		{
   905  			name: "valid label",
   906  			ammp: &AzureManagedMachinePool{
   907  				Spec: AzureManagedMachinePoolSpec{
   908  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   909  						Mode:   "User",
   910  						OSType: ptr.To(LinuxOS),
   911  						NodeLabels: map[string]string{
   912  							"foo": "bar",
   913  						},
   914  					},
   915  				},
   916  			},
   917  			wantErr: false,
   918  		},
   919  		{
   920  			name: "kubernetes.azure.com label",
   921  			ammp: &AzureManagedMachinePool{
   922  				Spec: AzureManagedMachinePoolSpec{
   923  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   924  						Mode:   "User",
   925  						OSType: ptr.To(LinuxOS),
   926  						NodeLabels: map[string]string{
   927  							"kubernetes.azure.com/scalesetpriority": "spot",
   928  						},
   929  					},
   930  				},
   931  			},
   932  			wantErr:  true,
   933  			errorLen: 1,
   934  		},
   935  		{
   936  			name: "pool with invalid public ip prefix",
   937  			ammp: &AzureManagedMachinePool{
   938  				Spec: AzureManagedMachinePoolSpec{
   939  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   940  						EnableNodePublicIP:   ptr.To(true),
   941  						NodePublicIPPrefixID: ptr.To("not a valid resource ID"),
   942  					},
   943  				},
   944  			},
   945  			wantErr:  true,
   946  			errorLen: 1,
   947  		},
   948  		{
   949  			name: "pool with public ip prefix cannot omit node public IP",
   950  			ammp: &AzureManagedMachinePool{
   951  				Spec: AzureManagedMachinePoolSpec{
   952  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   953  						EnableNodePublicIP:   nil,
   954  						NodePublicIPPrefixID: ptr.To("subscriptions/11111111-2222-aaaa-bbbb-cccccccccccc/resourceGroups/public-ip-test/providers/Microsoft.Network/publicipprefixes/public-ip-prefix"),
   955  					},
   956  				},
   957  			},
   958  			wantErr:  true,
   959  			errorLen: 1,
   960  		},
   961  		{
   962  			name: "pool with public ip prefix cannot disable node public IP",
   963  			ammp: &AzureManagedMachinePool{
   964  				Spec: AzureManagedMachinePoolSpec{
   965  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   966  						EnableNodePublicIP:   ptr.To(false),
   967  						NodePublicIPPrefixID: ptr.To("subscriptions/11111111-2222-aaaa-bbbb-cccccccccccc/resourceGroups/public-ip-test/providers/Microsoft.Network/publicipprefixes/public-ip-prefix"),
   968  					},
   969  				},
   970  			},
   971  			wantErr:  true,
   972  			errorLen: 1,
   973  		},
   974  		{
   975  			name: "pool with public ip prefix with node public IP enabled ok",
   976  			ammp: &AzureManagedMachinePool{
   977  				Spec: AzureManagedMachinePoolSpec{
   978  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   979  						EnableNodePublicIP:   ptr.To(true),
   980  						NodePublicIPPrefixID: ptr.To("subscriptions/11111111-2222-aaaa-bbbb-cccccccccccc/resourceGroups/public-ip-test/providers/Microsoft.Network/publicipprefixes/public-ip-prefix"),
   981  					},
   982  				},
   983  			},
   984  			wantErr: false,
   985  		},
   986  		{
   987  			name: "pool with public ip prefix with leading slash with node public IP enabled ok",
   988  			ammp: &AzureManagedMachinePool{
   989  				Spec: AzureManagedMachinePoolSpec{
   990  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
   991  						EnableNodePublicIP:   ptr.To(true),
   992  						NodePublicIPPrefixID: ptr.To("/subscriptions/11111111-2222-aaaa-bbbb-cccccccccccc/resourceGroups/public-ip-test/providers/Microsoft.Network/publicipprefixes/public-ip-prefix"),
   993  					},
   994  				},
   995  			},
   996  			wantErr: false,
   997  		},
   998  		{
   999  			name: "pool without public ip prefix with node public IP unset ok",
  1000  			ammp: &AzureManagedMachinePool{
  1001  				Spec: AzureManagedMachinePoolSpec{
  1002  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1003  						EnableNodePublicIP: nil,
  1004  					},
  1005  				},
  1006  			},
  1007  			wantErr: false,
  1008  		},
  1009  		{
  1010  			name: "pool without public ip prefix with node public IP enabled ok",
  1011  			ammp: &AzureManagedMachinePool{
  1012  				Spec: AzureManagedMachinePoolSpec{
  1013  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1014  						EnableNodePublicIP: ptr.To(true),
  1015  					},
  1016  				},
  1017  			},
  1018  			wantErr: false,
  1019  		},
  1020  		{
  1021  			name: "pool without public ip prefix with node public IP disabled ok",
  1022  			ammp: &AzureManagedMachinePool{
  1023  				Spec: AzureManagedMachinePoolSpec{
  1024  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1025  						EnableNodePublicIP: ptr.To(false),
  1026  					},
  1027  				},
  1028  			},
  1029  			wantErr: false,
  1030  		},
  1031  		{
  1032  			name: "KubeletConfig CPUCfsQuotaPeriod needs 'ms' suffix",
  1033  			ammp: &AzureManagedMachinePool{
  1034  				Spec: AzureManagedMachinePoolSpec{
  1035  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1036  						KubeletConfig: &KubeletConfig{
  1037  							CPUCfsQuotaPeriod: ptr.To("100"),
  1038  						},
  1039  					},
  1040  				},
  1041  			},
  1042  			wantErr:  true,
  1043  			errorLen: 1,
  1044  		},
  1045  		{
  1046  			name: "KubeletConfig CPUCfsQuotaPeriod has valid 'ms' suffix",
  1047  			ammp: &AzureManagedMachinePool{
  1048  				Spec: AzureManagedMachinePoolSpec{
  1049  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1050  						KubeletConfig: &KubeletConfig{
  1051  							CPUCfsQuotaPeriod: ptr.To("100ms"),
  1052  						},
  1053  					},
  1054  				},
  1055  			},
  1056  			wantErr: false,
  1057  		},
  1058  		{
  1059  			name: "KubeletConfig ImageGcLowThreshold can't be more than ImageGcHighThreshold",
  1060  			ammp: &AzureManagedMachinePool{
  1061  				Spec: AzureManagedMachinePoolSpec{
  1062  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1063  						KubeletConfig: &KubeletConfig{
  1064  							ImageGcLowThreshold:  ptr.To(100),
  1065  							ImageGcHighThreshold: ptr.To(99),
  1066  						},
  1067  					},
  1068  				},
  1069  			},
  1070  			wantErr:  true,
  1071  			errorLen: 1,
  1072  		},
  1073  		{
  1074  			name: "KubeletConfig ImageGcLowThreshold is lower than ImageGcHighThreshold",
  1075  			ammp: &AzureManagedMachinePool{
  1076  				Spec: AzureManagedMachinePoolSpec{
  1077  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1078  						KubeletConfig: &KubeletConfig{
  1079  							ImageGcLowThreshold:  ptr.To(99),
  1080  							ImageGcHighThreshold: ptr.To(100),
  1081  						},
  1082  					},
  1083  				},
  1084  			},
  1085  			wantErr: false,
  1086  		},
  1087  		{
  1088  			name: "valid KubeletConfig AllowedUnsafeSysctls values",
  1089  			ammp: &AzureManagedMachinePool{
  1090  				Spec: AzureManagedMachinePoolSpec{
  1091  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1092  						KubeletConfig: &KubeletConfig{
  1093  							AllowedUnsafeSysctls: []string{
  1094  								"kernel.shm*",
  1095  								"kernel.msg*",
  1096  								"kernel.sem",
  1097  								"fs.mqueue.*",
  1098  								"net.*",
  1099  							},
  1100  						},
  1101  					},
  1102  				},
  1103  			},
  1104  			wantErr: false,
  1105  		},
  1106  		{
  1107  			name: "more valid KubeletConfig AllowedUnsafeSysctls values",
  1108  			ammp: &AzureManagedMachinePool{
  1109  				Spec: AzureManagedMachinePoolSpec{
  1110  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1111  						KubeletConfig: &KubeletConfig{
  1112  							AllowedUnsafeSysctls: []string{
  1113  								"kernel.shm.something",
  1114  								"kernel.msg.foo.bar",
  1115  								"kernel.sem",
  1116  								"fs.mqueue.baz",
  1117  								"net.my.configuration.path",
  1118  							},
  1119  						},
  1120  					},
  1121  				},
  1122  			},
  1123  			wantErr: false,
  1124  		},
  1125  		{
  1126  			name: "an invalid KubeletConfig AllowedUnsafeSysctls value in a set",
  1127  			ammp: &AzureManagedMachinePool{
  1128  				Spec: AzureManagedMachinePoolSpec{
  1129  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1130  						KubeletConfig: &KubeletConfig{
  1131  							AllowedUnsafeSysctls: []string{
  1132  								"kernel.shm.something",
  1133  								"kernel.msg.foo.bar",
  1134  								"kernel.sem",
  1135  								"fs.mqueue.baz",
  1136  								"net.my.configuration.path",
  1137  								"kernel.not.allowed",
  1138  							},
  1139  						},
  1140  					},
  1141  				},
  1142  			},
  1143  			wantErr:  true,
  1144  			errorLen: 1,
  1145  		},
  1146  		{
  1147  			name: "validLinuxOSConfig Sysctls NetIpv4IpLocalPortRange.First is less than NetIpv4IpLocalPortRange.Last",
  1148  			ammp: &AzureManagedMachinePool{
  1149  				Spec: AzureManagedMachinePoolSpec{
  1150  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1151  						LinuxOSConfig: &LinuxOSConfig{
  1152  							Sysctls: &SysctlConfig{
  1153  								NetIpv4IPLocalPortRange: ptr.To("2000 33000"),
  1154  							},
  1155  						},
  1156  					},
  1157  				},
  1158  			},
  1159  			wantErr: false,
  1160  		},
  1161  		{
  1162  			name: "an invalid LinuxOSConfig Sysctls NetIpv4IpLocalPortRange.First string is ill-formed",
  1163  			ammp: &AzureManagedMachinePool{
  1164  				Spec: AzureManagedMachinePoolSpec{
  1165  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1166  						LinuxOSConfig: &LinuxOSConfig{
  1167  							Sysctls: &SysctlConfig{
  1168  								NetIpv4IPLocalPortRange: ptr.To("wrong 33000"),
  1169  							},
  1170  						},
  1171  					},
  1172  				},
  1173  			},
  1174  			wantErr:  true,
  1175  			errorLen: 1,
  1176  		},
  1177  		{
  1178  			name: "an invalid LinuxOSConfig Sysctls NetIpv4IpLocalPortRange.Last string is ill-formed",
  1179  			ammp: &AzureManagedMachinePool{
  1180  				Spec: AzureManagedMachinePoolSpec{
  1181  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1182  						LinuxOSConfig: &LinuxOSConfig{
  1183  							Sysctls: &SysctlConfig{
  1184  								NetIpv4IPLocalPortRange: ptr.To("2000 wrong"),
  1185  							},
  1186  						},
  1187  					},
  1188  				},
  1189  			},
  1190  			wantErr:  true,
  1191  			errorLen: 1,
  1192  		},
  1193  		{
  1194  			name: "an invalid LinuxOSConfig Sysctls NetIpv4IpLocalPortRange.First less than allowed value",
  1195  			ammp: &AzureManagedMachinePool{
  1196  				Spec: AzureManagedMachinePoolSpec{
  1197  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1198  						LinuxOSConfig: &LinuxOSConfig{
  1199  							Sysctls: &SysctlConfig{
  1200  								NetIpv4IPLocalPortRange: ptr.To("1020 32999"),
  1201  							},
  1202  						},
  1203  					},
  1204  				},
  1205  			},
  1206  			wantErr:  true,
  1207  			errorLen: 1,
  1208  		},
  1209  		{
  1210  			name: "an invalid LinuxOSConfig Sysctls NetIpv4IpLocalPortRange.Last less than allowed value",
  1211  			ammp: &AzureManagedMachinePool{
  1212  				Spec: AzureManagedMachinePoolSpec{
  1213  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1214  						LinuxOSConfig: &LinuxOSConfig{
  1215  							Sysctls: &SysctlConfig{
  1216  								NetIpv4IPLocalPortRange: ptr.To("1024 32000"),
  1217  							},
  1218  						},
  1219  					},
  1220  				},
  1221  			},
  1222  			wantErr:  true,
  1223  			errorLen: 1,
  1224  		},
  1225  		{
  1226  			name: "an invalid LinuxOSConfig Sysctls NetIpv4IpLocalPortRange.First is greater than NetIpv4IpLocalPortRange.Last",
  1227  			ammp: &AzureManagedMachinePool{
  1228  				Spec: AzureManagedMachinePoolSpec{
  1229  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1230  						LinuxOSConfig: &LinuxOSConfig{
  1231  							Sysctls: &SysctlConfig{
  1232  								NetIpv4IPLocalPortRange: ptr.To("33000 32999"),
  1233  							},
  1234  						},
  1235  					},
  1236  				},
  1237  			},
  1238  			wantErr:  true,
  1239  			errorLen: 1,
  1240  		},
  1241  		{
  1242  			name: "valid LinuxOSConfig Sysctls is set by disabling FailSwapOn",
  1243  			ammp: &AzureManagedMachinePool{
  1244  				Spec: AzureManagedMachinePoolSpec{
  1245  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1246  						KubeletConfig: &KubeletConfig{
  1247  							FailSwapOn: ptr.To(false),
  1248  						},
  1249  						LinuxOSConfig: &LinuxOSConfig{
  1250  							SwapFileSizeMB: ptr.To(1500),
  1251  						},
  1252  					},
  1253  				},
  1254  			},
  1255  			wantErr: false,
  1256  		},
  1257  		{
  1258  			name: "an invalid LinuxOSConfig Sysctls is set with FailSwapOn set to true",
  1259  			ammp: &AzureManagedMachinePool{
  1260  				Spec: AzureManagedMachinePoolSpec{
  1261  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1262  						KubeletConfig: &KubeletConfig{
  1263  							FailSwapOn: ptr.To(true),
  1264  						},
  1265  						LinuxOSConfig: &LinuxOSConfig{
  1266  							SwapFileSizeMB: ptr.To(1500),
  1267  						},
  1268  					},
  1269  				},
  1270  			},
  1271  			wantErr:  true,
  1272  			errorLen: 1,
  1273  		},
  1274  		{
  1275  			name: "an invalid LinuxOSConfig Sysctls is set without disabling FailSwapOn",
  1276  			ammp: &AzureManagedMachinePool{
  1277  				Spec: AzureManagedMachinePoolSpec{
  1278  					AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1279  						LinuxOSConfig: &LinuxOSConfig{
  1280  							SwapFileSizeMB: ptr.To(1500),
  1281  						},
  1282  					},
  1283  				},
  1284  			},
  1285  			wantErr:  true,
  1286  			errorLen: 1,
  1287  		},
  1288  	}
  1289  
  1290  	var client client.Client
  1291  	for _, tc := range tests {
  1292  		t.Run(tc.name, func(t *testing.T) {
  1293  			g := NewWithT(t)
  1294  			mw := &azureManagedMachinePoolWebhook{
  1295  				Client: client,
  1296  			}
  1297  			_, err := mw.ValidateCreate(context.Background(), tc.ammp)
  1298  			if tc.wantErr {
  1299  				g.Expect(err).To(HaveOccurred())
  1300  				g.Expect(err).To(HaveLen(tc.errorLen))
  1301  			} else {
  1302  				g.Expect(err).NotTo(HaveOccurred())
  1303  			}
  1304  		})
  1305  	}
  1306  }
  1307  
  1308  func TestAzureManagedMachinePool_ValidateCreateFailure(t *testing.T) {
  1309  	tests := []struct {
  1310  		name               string
  1311  		ammp               *AzureManagedMachinePool
  1312  		featureGateEnabled *bool
  1313  		expectError        bool
  1314  	}{
  1315  		{
  1316  			name:               "feature gate explicitly disabled",
  1317  			ammp:               getKnownValidAzureManagedMachinePool(),
  1318  			featureGateEnabled: ptr.To(false),
  1319  			expectError:        true,
  1320  		},
  1321  		{
  1322  			name:               "feature gate implicitly enabled",
  1323  			ammp:               getKnownValidAzureManagedMachinePool(),
  1324  			featureGateEnabled: nil,
  1325  			expectError:        false,
  1326  		},
  1327  	}
  1328  	for _, tc := range tests {
  1329  		t.Run(tc.name, func(t *testing.T) {
  1330  			if tc.featureGateEnabled != nil {
  1331  				defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, capifeature.MachinePool, *tc.featureGateEnabled)()
  1332  			}
  1333  			g := NewWithT(t)
  1334  			mw := &azureManagedMachinePoolWebhook{}
  1335  			_, err := mw.ValidateCreate(context.Background(), tc.ammp)
  1336  			if tc.expectError {
  1337  				g.Expect(err).To(HaveOccurred())
  1338  			} else {
  1339  				g.Expect(err).NotTo(HaveOccurred())
  1340  			}
  1341  		})
  1342  	}
  1343  }
  1344  
  1345  func TestAzureManagedMachinePool_validateLastSystemNodePool(t *testing.T) {
  1346  	deletionTime := metav1.Now()
  1347  	finalizers := []string{"test"}
  1348  	systemMachinePool := getManagedMachinePoolWithSystemMode()
  1349  	systemMachinePoolWithDeletionAnnotation := getAzureManagedMachinePoolWithChanges(
  1350  		// Add the DeleteForMoveAnnotation annotation to the AMMP
  1351  		func(azureManagedMachinePool *AzureManagedMachinePool) {
  1352  			azureManagedMachinePool.Annotations = map[string]string{
  1353  				clusterctlv1alpha3.DeleteForMoveAnnotation: "true",
  1354  			}
  1355  		},
  1356  	)
  1357  	tests := []struct {
  1358  		name    string
  1359  		ammp    *AzureManagedMachinePool
  1360  		cluster *clusterv1.Cluster
  1361  		wantErr bool
  1362  	}{
  1363  		{
  1364  			// AzureManagedMachinePool will be deleted since AMMP has DeleteForMoveAnnotation annotation
  1365  			// Note that Owner Cluster's deletion timestamp is nil and Owner cluster being paused does not matter anymore.
  1366  			name: "AzureManagedMachinePool (AMMP) should be deleted if this AMMP has the annotation 'cluster.x-k8s.io/move-to-delete' with the owner cluster being paused and 'No' deletion timestamp",
  1367  			ammp: systemMachinePoolWithDeletionAnnotation,
  1368  			cluster: &clusterv1.Cluster{
  1369  				ObjectMeta: metav1.ObjectMeta{
  1370  					Name:       systemMachinePool.GetLabels()[clusterv1.ClusterNameLabel],
  1371  					Namespace:  systemMachinePool.Namespace,
  1372  					Finalizers: finalizers,
  1373  				},
  1374  			},
  1375  			wantErr: false,
  1376  		},
  1377  		{
  1378  			// AzureManagedMachinePool will be deleted since Owner Cluster has been marked for deletion
  1379  			name: "AzureManagedMachinePool should be deleted since the Cluster is paused with a deletion timestamp",
  1380  			ammp: systemMachinePool,
  1381  			cluster: &clusterv1.Cluster{
  1382  				ObjectMeta: metav1.ObjectMeta{
  1383  					Name:              systemMachinePool.GetLabels()[clusterv1.ClusterNameLabel],
  1384  					Namespace:         systemMachinePool.Namespace,
  1385  					DeletionTimestamp: &deletionTime,
  1386  					Finalizers:        finalizers,
  1387  				},
  1388  			},
  1389  			wantErr: false,
  1390  		},
  1391  		{
  1392  			name: "AzureManagedMachinePool should not be deleted without a deletion timestamp on Owner Cluster and having one system pool node(invalid delete)",
  1393  			ammp: systemMachinePool,
  1394  			cluster: &clusterv1.Cluster{
  1395  				ObjectMeta: metav1.ObjectMeta{
  1396  					Name:      systemMachinePool.GetLabels()[clusterv1.ClusterNameLabel],
  1397  					Namespace: systemMachinePool.Namespace,
  1398  				},
  1399  			},
  1400  			wantErr: true,
  1401  		},
  1402  		{
  1403  			name: "AzureManagedMachinePool should be deleted when Cluster is set with a deletion timestamp having one system pool node(valid delete)",
  1404  			ammp: systemMachinePool,
  1405  			cluster: &clusterv1.Cluster{
  1406  				ObjectMeta: metav1.ObjectMeta{
  1407  					Name:              systemMachinePool.GetLabels()[clusterv1.ClusterNameLabel],
  1408  					Namespace:         systemMachinePool.Namespace,
  1409  					DeletionTimestamp: &deletionTime,
  1410  					Finalizers:        finalizers,
  1411  				},
  1412  			},
  1413  			wantErr: false,
  1414  		},
  1415  	}
  1416  
  1417  	for _, tc := range tests {
  1418  		t.Run(tc.name, func(t *testing.T) {
  1419  			g := NewWithT(t)
  1420  			scheme := runtime.NewScheme()
  1421  			_ = AddToScheme(scheme)
  1422  			_ = clusterv1.AddToScheme(scheme)
  1423  			fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.cluster, tc.ammp).Build()
  1424  			err := validateLastSystemNodePool(fakeClient, tc.ammp.Spec.NodeLabels, tc.ammp.Namespace, tc.ammp.Annotations)
  1425  			if tc.wantErr {
  1426  				g.Expect(err).To(HaveOccurred())
  1427  			} else {
  1428  				g.Expect(err).NotTo(HaveOccurred())
  1429  			}
  1430  		})
  1431  	}
  1432  }
  1433  
  1434  func getKnownValidAzureManagedMachinePool() *AzureManagedMachinePool {
  1435  	return &AzureManagedMachinePool{
  1436  		Spec: AzureManagedMachinePoolSpec{
  1437  			AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1438  				MaxPods:    ptr.To(30),
  1439  				OsDiskType: ptr.To(string(asocontainerservicev1.OSDiskType_Ephemeral)),
  1440  			},
  1441  		},
  1442  	}
  1443  }
  1444  
  1445  func getManagedMachinePoolWithSystemMode() *AzureManagedMachinePool {
  1446  	return &AzureManagedMachinePool{
  1447  		ObjectMeta: metav1.ObjectMeta{
  1448  			Namespace: metav1.NamespaceDefault,
  1449  			Labels: map[string]string{
  1450  				clusterv1.ClusterNameLabel: "test-cluster",
  1451  				LabelAgentPoolMode:         string(NodePoolModeSystem),
  1452  			},
  1453  		},
  1454  		Spec: AzureManagedMachinePoolSpec{
  1455  			AzureManagedMachinePoolClassSpec: AzureManagedMachinePoolClassSpec{
  1456  				NodeLabels: map[string]string{
  1457  					clusterv1.ClusterNameLabel: "test-cluster",
  1458  				},
  1459  			},
  1460  		},
  1461  	}
  1462  }
  1463  
  1464  func getAzureManagedMachinePoolWithChanges(changes ...func(*AzureManagedMachinePool)) *AzureManagedMachinePool {
  1465  	ammp := getManagedMachinePoolWithSystemMode().DeepCopy()
  1466  	for _, change := range changes {
  1467  		change(ammp)
  1468  	}
  1469  	return ammp
  1470  }