sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremanagedcontrolplanetemplate_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 . "github.com/onsi/gomega" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/utils/ptr" 26 ) 27 28 func TestControlPlaneTemplateDefaultingWebhook(t *testing.T) { 29 g := NewWithT(t) 30 31 t.Logf("Testing amcp defaulting webhook with no baseline") 32 amcpt := getAzureManagedControlPlaneTemplate() 33 mcptw := &azureManagedControlPlaneTemplateWebhook{} 34 err := mcptw.Default(context.Background(), amcpt) 35 g.Expect(err).NotTo(HaveOccurred()) 36 g.Expect(*amcpt.Spec.Template.Spec.NetworkPlugin).To(Equal("azure")) 37 g.Expect(*amcpt.Spec.Template.Spec.LoadBalancerSKU).To(Equal("Standard")) 38 g.Expect(amcpt.Spec.Template.Spec.Version).To(Equal("v1.17.5")) 39 g.Expect(amcpt.Spec.Template.Spec.VirtualNetwork.Name).To(Equal("fooName")) 40 g.Expect(amcpt.Spec.Template.Spec.VirtualNetwork.CIDRBlock).To(Equal(defaultAKSVnetCIDR)) 41 g.Expect(amcpt.Spec.Template.Spec.VirtualNetwork.Subnet.Name).To(Equal("fooName")) 42 g.Expect(amcpt.Spec.Template.Spec.VirtualNetwork.Subnet.CIDRBlock).To(Equal(defaultAKSNodeSubnetCIDR)) 43 g.Expect(amcpt.Spec.Template.Spec.SKU.Tier).To(Equal(FreeManagedControlPlaneTier)) 44 g.Expect(*amcpt.Spec.Template.Spec.EnablePreviewFeatures).To(BeFalse()) 45 46 t.Logf("Testing amcp defaulting webhook with baseline") 47 netPlug := "kubenet" 48 lbSKU := "Basic" 49 netPol := "azure" 50 amcpt.Spec.Template.Spec.NetworkPlugin = &netPlug 51 amcpt.Spec.Template.Spec.LoadBalancerSKU = &lbSKU 52 amcpt.Spec.Template.Spec.NetworkPolicy = &netPol 53 amcpt.Spec.Template.Spec.Version = "9.99.99" 54 amcpt.Spec.Template.Spec.VirtualNetwork.Name = "fooVnetName" 55 amcpt.Spec.Template.Spec.VirtualNetwork.Subnet.Name = "fooSubnetName" 56 amcpt.Spec.Template.Spec.SKU.Tier = PaidManagedControlPlaneTier 57 amcpt.Spec.Template.Spec.EnablePreviewFeatures = ptr.To(true) 58 59 err = mcptw.Default(context.Background(), amcpt) 60 g.Expect(err).NotTo(HaveOccurred()) 61 g.Expect(*amcpt.Spec.Template.Spec.NetworkPlugin).To(Equal(netPlug)) 62 g.Expect(*amcpt.Spec.Template.Spec.LoadBalancerSKU).To(Equal(lbSKU)) 63 g.Expect(*amcpt.Spec.Template.Spec.NetworkPolicy).To(Equal(netPol)) 64 g.Expect(amcpt.Spec.Template.Spec.Version).To(Equal("v9.99.99")) 65 g.Expect(amcpt.Spec.Template.Spec.VirtualNetwork.Name).To(Equal("fooVnetName")) 66 g.Expect(amcpt.Spec.Template.Spec.VirtualNetwork.Subnet.Name).To(Equal("fooSubnetName")) 67 g.Expect(amcpt.Spec.Template.Spec.SKU.Tier).To(Equal(StandardManagedControlPlaneTier)) 68 g.Expect(*amcpt.Spec.Template.Spec.EnablePreviewFeatures).To(BeTrue()) 69 } 70 71 func TestControlPlaneTemplateUpdateWebhook(t *testing.T) { 72 tests := []struct { 73 name string 74 oldControlPlaneTemplate *AzureManagedControlPlaneTemplate 75 controlPlaneTemplate *AzureManagedControlPlaneTemplate 76 wantErr bool 77 }{ 78 { 79 name: "azuremanagedcontrolplanetemplate no changes - valid spec", 80 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 81 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 82 wantErr: false, 83 }, 84 { 85 name: "azuremanagedcontrolplanetemplate subscriptionID is immutable", 86 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 87 cpt.Spec.Template.Spec.SubscriptionID = "foo" 88 }), 89 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 90 cpt.Spec.Template.Spec.SubscriptionID = "bar" 91 }), 92 wantErr: true, 93 }, 94 { 95 name: "azuremanagedcontrolplanetemplate location is immutable", 96 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 97 cpt.Spec.Template.Spec.Location = "foo" 98 }), 99 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 100 cpt.Spec.Template.Spec.Location = "bar" 101 }), 102 wantErr: true, 103 }, 104 { 105 name: "azuremanagedcontrolplanetemplate DNSServiceIP is immutable", 106 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 107 cpt.Spec.Template.Spec.DNSServiceIP = ptr.To("foo") 108 }), 109 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 110 cpt.Spec.Template.Spec.DNSServiceIP = ptr.To("bar") 111 }), 112 wantErr: true, 113 }, 114 { 115 name: "azuremanagedcontrolplanetemplate NetworkPlugin is immutable", 116 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 117 cpt.Spec.Template.Spec.NetworkPlugin = ptr.To("foo") 118 }), 119 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 120 cpt.Spec.Template.Spec.NetworkPlugin = ptr.To("bar") 121 }), 122 wantErr: true, 123 }, 124 { 125 name: "azuremanagedcontrolplanetemplate NetworkPolicy is immutable", 126 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 127 cpt.Spec.Template.Spec.NetworkPolicy = ptr.To("foo") 128 }), 129 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 130 cpt.Spec.Template.Spec.NetworkPolicy = ptr.To("bar") 131 }), 132 wantErr: true, 133 }, 134 { 135 name: "azuremanagedcontrolplanetemplate LoadBalancerSKU is immutable", 136 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 137 cpt.Spec.Template.Spec.LoadBalancerSKU = ptr.To("foo") 138 }), 139 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 140 cpt.Spec.Template.Spec.LoadBalancerSKU = ptr.To("bar") 141 }), 142 wantErr: true, 143 }, 144 { 145 name: "cannot disable AADProfile", 146 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 147 cpt.Spec.Template.Spec.AADProfile = &AADProfile{ 148 Managed: true, 149 } 150 }), 151 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 152 wantErr: true, 153 }, 154 { 155 name: "cannot set AADProfile.Managed to false", 156 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 157 cpt.Spec.Template.Spec.AADProfile = &AADProfile{ 158 Managed: true, 159 } 160 }), 161 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 162 cpt.Spec.Template.Spec.AADProfile = &AADProfile{ 163 Managed: false, 164 } 165 }), 166 wantErr: true, 167 }, 168 { 169 name: "length of AADProfile.AdminGroupObjectIDs cannot be zero", 170 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 171 cpt.Spec.Template.Spec.AADProfile = &AADProfile{ 172 AdminGroupObjectIDs: []string{"foo"}, 173 } 174 }), 175 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 176 cpt.Spec.Template.Spec.AADProfile = &AADProfile{ 177 AdminGroupObjectIDs: []string{}, 178 } 179 }), 180 wantErr: true, 181 }, 182 { 183 name: "azuremanagedcontrolplanetemplate OutboundType is immutable", 184 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 185 cpt.Spec.Template.Spec.OutboundType = (*ManagedControlPlaneOutboundType)(ptr.To(string(ManagedControlPlaneOutboundTypeLoadBalancer))) 186 }), 187 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 188 cpt.Spec.Template.Spec.OutboundType = (*ManagedControlPlaneOutboundType)(ptr.To(string(ManagedControlPlaneOutboundTypeManagedNATGateway))) 189 }), 190 wantErr: true, 191 }, 192 { 193 name: "azuremanagedcontrolplanetemplate AKSExtension type and plan are immutable", 194 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 195 cpt.Spec.Template.Spec.Extensions = []AKSExtension{ 196 { 197 Name: "foo", 198 ExtensionType: ptr.To("foo-type"), 199 Plan: &ExtensionPlan{ 200 Name: "foo-name", 201 Product: "foo-product", 202 Publisher: "foo-publisher", 203 }, 204 }, 205 } 206 }), 207 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 208 cpt.Spec.Template.Spec.Extensions = []AKSExtension{ 209 { 210 Name: "foo", 211 ExtensionType: ptr.To("bar"), 212 Plan: &ExtensionPlan{ 213 Name: "bar-name", 214 Product: "bar-product", 215 Publisher: "bar-publisher", 216 }, 217 }, 218 } 219 }), 220 wantErr: true, 221 }, 222 { 223 name: "azuremanagedcontrolplanetemplate AKSExtension autoUpgradeMinorVersion is mutable", 224 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 225 cpt.Spec.Template.Spec.Extensions = []AKSExtension{ 226 { 227 Name: "foo", 228 ExtensionType: ptr.To("foo"), 229 AutoUpgradeMinorVersion: ptr.To(true), 230 Plan: &ExtensionPlan{ 231 Name: "bar-name", 232 Product: "bar-product", 233 Publisher: "bar-publisher", 234 }, 235 }, 236 } 237 }), 238 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 239 cpt.Spec.Template.Spec.Extensions = []AKSExtension{ 240 { 241 Name: "foo", 242 ExtensionType: ptr.To("foo"), 243 AutoUpgradeMinorVersion: ptr.To(false), 244 Plan: &ExtensionPlan{ 245 Name: "bar-name", 246 Product: "bar-product", 247 Publisher: "bar-publisher", 248 }, 249 }, 250 } 251 }), 252 wantErr: false, 253 }, 254 { 255 name: "azuremanagedcontrolplanetemplate networkDataplane is immutable", 256 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 257 cpt.Spec.Template.Spec.NetworkDataplane = ptr.To(NetworkDataplaneTypeAzure) 258 }), 259 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 260 cpt.Spec.Template.Spec.NetworkDataplane = ptr.To(NetworkDataplaneTypeCilium) 261 }), 262 wantErr: true, 263 }, 264 { 265 name: "AzureManagedControlPlane invalid version downgrade change", 266 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 267 cpt.Spec.Template.Spec.Version = "v1.18.0" 268 }), 269 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 270 cpt.Spec.Template.Spec.Version = "v1.17.0" 271 }), 272 wantErr: true, 273 }, 274 } 275 for _, tc := range tests { 276 t.Run(tc.name, func(t *testing.T) { 277 g := NewWithT(t) 278 cpw := &azureManagedControlPlaneTemplateWebhook{} 279 _, err := cpw.ValidateUpdate(context.Background(), tc.oldControlPlaneTemplate, tc.controlPlaneTemplate) 280 if tc.wantErr { 281 g.Expect(err).To(HaveOccurred()) 282 } else { 283 g.Expect(err).NotTo(HaveOccurred()) 284 } 285 }) 286 } 287 } 288 289 func TestValidateVirtualNetworkTemplateUpdate(t *testing.T) { 290 tests := []struct { 291 name string 292 oldControlPlaneTemplate *AzureManagedControlPlaneTemplate 293 controlPlaneTemplate *AzureManagedControlPlaneTemplate 294 wantErr bool 295 }{ 296 { 297 name: "azuremanagedcontrolplanetemplate no changes - valid spec", 298 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 299 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 300 wantErr: false, 301 }, 302 { 303 name: "azuremanagedcontrolplanetemplate name is immutable", 304 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 305 cpt.Spec.Template.Spec.VirtualNetwork = ManagedControlPlaneVirtualNetwork{ 306 ManagedControlPlaneVirtualNetworkClassSpec: ManagedControlPlaneVirtualNetworkClassSpec{ 307 Name: "fooName", 308 }, 309 } 310 }), 311 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 312 cpt.Spec.Template.Spec.VirtualNetwork = ManagedControlPlaneVirtualNetwork{ 313 ManagedControlPlaneVirtualNetworkClassSpec: ManagedControlPlaneVirtualNetworkClassSpec{ 314 Name: "barName", 315 }, 316 } 317 }), 318 wantErr: true, 319 }, 320 { 321 name: "azuremanagedcontrolplanetemplate networkCIDRBlock is immutable", 322 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 323 cpt.Spec.Template.Spec.VirtualNetwork = ManagedControlPlaneVirtualNetwork{ 324 ManagedControlPlaneVirtualNetworkClassSpec: ManagedControlPlaneVirtualNetworkClassSpec{ 325 CIDRBlock: "fooCIDRBlock", 326 }, 327 } 328 }), 329 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 330 cpt.Spec.Template.Spec.VirtualNetwork = ManagedControlPlaneVirtualNetwork{ 331 ManagedControlPlaneVirtualNetworkClassSpec: ManagedControlPlaneVirtualNetworkClassSpec{ 332 CIDRBlock: "barCIDRBlock", 333 }, 334 } 335 }), 336 wantErr: true, 337 }, 338 } 339 for _, tc := range tests { 340 t.Run(tc.name, func(t *testing.T) { 341 g := NewWithT(t) 342 allErrs := tc.controlPlaneTemplate.validateVirtualNetworkTemplateUpdate(tc.oldControlPlaneTemplate) 343 if tc.wantErr { 344 g.Expect(allErrs).NotTo(BeEmpty()) 345 } else { 346 g.Expect(allErrs).To(BeEmpty()) 347 } 348 }) 349 } 350 } 351 352 func TestValidateAPIServerAccessProfileUpdate(t *testing.T) { 353 tests := []struct { 354 name string 355 oldControlPlaneTemplate *AzureManagedControlPlaneTemplate 356 controlPlaneTemplate *AzureManagedControlPlaneTemplate 357 wantErr bool 358 }{ 359 { 360 name: "azuremanagedcontrolplanetemplate no changes - valid spec", 361 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 362 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(), 363 wantErr: false, 364 }, 365 { 366 name: "azuremanagedcontrolplanetemplate enablePrivateCluster is immutable", 367 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 368 cpt.Spec.Template.Spec.APIServerAccessProfile = &APIServerAccessProfile{ 369 APIServerAccessProfileClassSpec: APIServerAccessProfileClassSpec{ 370 EnablePrivateCluster: ptr.To(true), 371 }, 372 } 373 }), 374 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 375 cpt.Spec.Template.Spec.APIServerAccessProfile = &APIServerAccessProfile{ 376 APIServerAccessProfileClassSpec: APIServerAccessProfileClassSpec{ 377 EnablePrivateCluster: ptr.To(false), 378 }, 379 } 380 }), 381 wantErr: true, 382 }, 383 { 384 name: "azuremanagedcontrolplanetemplate privateDNSZone is immutable", 385 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 386 cpt.Spec.Template.Spec.APIServerAccessProfile = &APIServerAccessProfile{ 387 APIServerAccessProfileClassSpec: APIServerAccessProfileClassSpec{ 388 PrivateDNSZone: ptr.To("foo"), 389 }, 390 } 391 }), 392 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 393 cpt.Spec.Template.Spec.APIServerAccessProfile = &APIServerAccessProfile{ 394 APIServerAccessProfileClassSpec: APIServerAccessProfileClassSpec{ 395 PrivateDNSZone: ptr.To("bar"), 396 }, 397 } 398 }), 399 wantErr: true, 400 }, 401 { 402 name: "azuremanagedcontrolplanetemplate enablePrivateClusterPublicFQDN is immutable", 403 oldControlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 404 cpt.Spec.Template.Spec.APIServerAccessProfile = &APIServerAccessProfile{ 405 APIServerAccessProfileClassSpec: APIServerAccessProfileClassSpec{ 406 EnablePrivateClusterPublicFQDN: ptr.To(true), 407 }, 408 } 409 }), 410 controlPlaneTemplate: getAzureManagedControlPlaneTemplate(func(cpt *AzureManagedControlPlaneTemplate) { 411 cpt.Spec.Template.Spec.APIServerAccessProfile = &APIServerAccessProfile{ 412 APIServerAccessProfileClassSpec: APIServerAccessProfileClassSpec{ 413 EnablePrivateClusterPublicFQDN: ptr.To(false), 414 }, 415 } 416 }), 417 wantErr: true, 418 }, 419 } 420 for _, tc := range tests { 421 t.Run(tc.name, func(t *testing.T) { 422 g := NewWithT(t) 423 allErrs := tc.controlPlaneTemplate.validateAPIServerAccessProfileTemplateUpdate(tc.oldControlPlaneTemplate) 424 if tc.wantErr { 425 g.Expect(allErrs).NotTo(BeEmpty()) 426 } else { 427 g.Expect(allErrs).To(BeEmpty()) 428 } 429 }) 430 } 431 } 432 433 func getAzureManagedControlPlaneTemplate(changes ...func(*AzureManagedControlPlaneTemplate)) *AzureManagedControlPlaneTemplate { 434 input := &AzureManagedControlPlaneTemplate{ 435 ObjectMeta: metav1.ObjectMeta{ 436 Name: "fooName", 437 }, 438 Spec: AzureManagedControlPlaneTemplateSpec{ 439 Template: AzureManagedControlPlaneTemplateResource{ 440 Spec: AzureManagedControlPlaneTemplateResourceSpec{ 441 AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{ 442 Location: "fooLocation", 443 Version: "v1.17.5", 444 }, 445 }, 446 }, 447 }, 448 } 449 450 for _, change := range changes { 451 change(input) 452 } 453 454 return input 455 }