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 }