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