sigs.k8s.io/cluster-api-provider-azure@v1.17.0/api/v1beta1/azuremachine_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 "github.com/pkg/errors" 26 corev1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/utils/ptr" 30 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 ) 33 34 var ( 35 validSSHPublicKey = generateSSHPublicKey(true) 36 validOSDisk = generateValidOSDisk() 37 ) 38 39 func TestAzureMachine_ValidateCreate(t *testing.T) { 40 tests := []struct { 41 name string 42 machine *AzureMachine 43 wantErr bool 44 }{ 45 { 46 name: "azuremachine with marketplace image - full", 47 machine: createMachineWithMarketPlaceImage("PUB1234", "OFFER1234", "SKU1234", "1.0.0"), 48 wantErr: false, 49 }, 50 { 51 name: "azuremachine with marketplace image - missing publisher", 52 machine: createMachineWithMarketPlaceImage("", "OFFER1235", "SKU1235", "2.0.0"), 53 wantErr: true, 54 }, 55 { 56 name: "azuremachine with shared gallery image - full", 57 machine: createMachineWithSharedImage("SUB123", "RG123", "NAME123", "GALLERY1", "1.0.0"), 58 wantErr: false, 59 }, 60 { 61 name: "azuremachine with marketplace image - missing subscription", 62 machine: createMachineWithSharedImage("", "RG124", "NAME124", "GALLERY1", "2.0.0"), 63 wantErr: true, 64 }, 65 { 66 name: "azuremachine with image by - with id", 67 machine: createMachineWithImageByID("ID123"), 68 wantErr: false, 69 }, 70 { 71 name: "azuremachine with image by - without id", 72 machine: createMachineWithImageByID(""), 73 wantErr: true, 74 }, 75 { 76 name: "azuremachine with valid SSHPublicKey", 77 machine: createMachineWithSSHPublicKey(validSSHPublicKey), 78 wantErr: false, 79 }, 80 { 81 name: "azuremachine without SSHPublicKey", 82 machine: createMachineWithSSHPublicKey(""), 83 wantErr: true, 84 }, 85 { 86 name: "azuremachine with invalid SSHPublicKey", 87 machine: createMachineWithSSHPublicKey("invalid ssh key"), 88 wantErr: true, 89 }, 90 { 91 name: "azuremachine with list of user-assigned identities", 92 machine: createMachineWithUserAssignedIdentities([]UserAssignedIdentity{ 93 {ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-12345-control-plane-9d5x5"}, 94 {ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-12345-control-plane-a1b2c"}, 95 }), 96 wantErr: false, 97 }, 98 { 99 name: "azuremachine with list of user-assigned identities with wrong identity type", 100 machine: createMachineWithUserAssignedIdentitiesWithBadIdentity([]UserAssignedIdentity{ 101 {ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-12345-control-plane-9d5x5"}, 102 {ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-12345-control-plane-a1b2c"}, 103 }), 104 wantErr: true, 105 }, 106 { 107 name: "azuremachine with empty list of user-assigned identities", 108 machine: createMachineWithUserAssignedIdentities([]UserAssignedIdentity{}), 109 wantErr: true, 110 }, 111 { 112 name: "azuremachine with valid osDisk cache type", 113 machine: createMachineWithOsDiskCacheType(string(armcompute.PossibleCachingTypesValues()[1])), 114 wantErr: false, 115 }, 116 { 117 name: "azuremachine with invalid osDisk cache type", 118 machine: createMachineWithOsDiskCacheType("invalid_cache_type"), 119 wantErr: true, 120 }, 121 { 122 name: "azuremachinepool with managed diagnostics profile", 123 machine: createMachineWithDiagnostics(ManagedDiagnosticsStorage, nil), 124 wantErr: false, 125 }, 126 { 127 name: "azuremachine with disabled diagnostics profile", 128 machine: createMachineWithDiagnostics(ManagedDiagnosticsStorage, nil), 129 wantErr: false, 130 }, 131 { 132 name: "azuremachine with user managed diagnostics profile and defined user managed storage account", 133 machine: createMachineWithDiagnostics(UserManagedDiagnosticsStorage, &UserManagedBootDiagnostics{StorageAccountURI: "https://fakeurl"}), 134 wantErr: false, 135 }, 136 { 137 name: "azuremachine with empty diagnostics profile", 138 machine: createMachineWithDiagnostics("", nil), 139 wantErr: false, 140 }, 141 { 142 name: "azuremachine with user managed diagnostics profile, but empty user managed storage account", 143 machine: createMachineWithDiagnostics(UserManagedDiagnosticsStorage, nil), 144 wantErr: true, 145 }, 146 { 147 name: "azuremachine with invalid network configuration", 148 machine: createMachineWithNetworkConfig("subnet", nil, []NetworkInterface{{SubnetName: "subnet1"}}), 149 wantErr: true, 150 }, 151 { 152 name: "azuremachine with valid legacy network configuration", 153 machine: createMachineWithNetworkConfig("subnet", nil, []NetworkInterface{}), 154 wantErr: false, 155 }, 156 { 157 name: "azuremachine with valid network configuration", 158 machine: createMachineWithNetworkConfig("", nil, []NetworkInterface{{SubnetName: "subnet", PrivateIPConfigs: 1}}), 159 wantErr: false, 160 }, 161 { 162 name: "azuremachine without confidential compute properties and encryption at host enabled", 163 machine: createMachineWithConfidentialCompute("", "", true, false, false), 164 wantErr: false, 165 }, 166 { 167 name: "azuremachine with confidential compute VMGuestStateOnly encryption and encryption at host enabled", 168 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeVMGuestStateOnly, SecurityTypesConfidentialVM, true, false, false), 169 wantErr: true, 170 }, 171 { 172 name: "azuremachine with confidential compute DiskWithVMGuestState encryption and encryption at host enabled", 173 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, SecurityTypesConfidentialVM, true, true, true), 174 wantErr: true, 175 }, 176 { 177 name: "azuremachine with confidential compute VMGuestStateOnly encryption, vTPM and SecureBoot enabled", 178 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeVMGuestStateOnly, SecurityTypesConfidentialVM, false, true, true), 179 wantErr: false, 180 }, 181 { 182 name: "azuremachine with confidential compute VMGuestStateOnly encryption enabled, vTPM enabled and SecureBoot disabled", 183 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeVMGuestStateOnly, SecurityTypesConfidentialVM, false, true, false), 184 wantErr: false, 185 }, 186 { 187 name: "azuremachine with confidential compute VMGuestStateOnly encryption enabled, vTPM disabled and SecureBoot enabled", 188 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeVMGuestStateOnly, SecurityTypesConfidentialVM, false, false, true), 189 wantErr: true, 190 }, 191 { 192 name: "azuremachine with confidential compute VMGuestStateOnly encryption enabled, vTPM enabled, SecureBoot disabled and SecurityType empty", 193 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeVMGuestStateOnly, "", false, true, false), 194 wantErr: true, 195 }, 196 { 197 name: "azuremachine with confidential compute VMGuestStateOnly encryption enabled, vTPM and SecureBoot empty", 198 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeVMGuestStateOnly, SecurityTypesConfidentialVM, false, false, false), 199 wantErr: true, 200 }, 201 { 202 name: "azuremachine with confidential compute DiskWithVMGuestState encryption, vTPM and SecureBoot enabled", 203 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, SecurityTypesConfidentialVM, false, true, true), 204 wantErr: false, 205 }, 206 { 207 name: "azuremachine with confidential compute DiskWithVMGuestState encryption enabled, vTPM enabled and SecureBoot disabled", 208 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, SecurityTypesConfidentialVM, false, true, false), 209 wantErr: true, 210 }, 211 { 212 name: "azuremachine with confidential compute DiskWithVMGuestState encryption enabled, vTPM disabled and SecureBoot enabled", 213 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, SecurityTypesConfidentialVM, false, false, true), 214 wantErr: true, 215 }, 216 { 217 name: "azuremachine with confidential compute DiskWithVMGuestState encryption enabled, vTPM disabled and SecureBoot disabled", 218 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, SecurityTypesConfidentialVM, false, false, false), 219 wantErr: true, 220 }, 221 { 222 name: "azuremachine with confidential compute DiskWithVMGuestState encryption enabled, vTPM enabled, SecureBoot disabled and SecurityType empty", 223 machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, "", false, true, false), 224 wantErr: true, 225 }, 226 { 227 name: "azuremachine with empty capacity reservation group id", 228 machine: createMachineWithCapacityReservaionGroupID(""), 229 wantErr: false, 230 }, 231 { 232 name: "azuremachine with valid capacity reservation group id", 233 machine: createMachineWithCapacityReservaionGroupID("azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/capacityReservationGroups/capacity-reservation-group-name"), 234 wantErr: false, 235 }, 236 { 237 name: "azuremachine with invalid capacity reservation group id", 238 machine: createMachineWithCapacityReservaionGroupID("invalid-capacity-group-id"), 239 wantErr: true, 240 }, 241 { 242 name: "azuremachine with DisableExtensionOperations true and without VMExtensions", 243 machine: createMachineWithDisableExtenionOperations(), 244 wantErr: false, 245 }, 246 { 247 name: "azuremachine with DisableExtensionOperations true and with VMExtension", 248 machine: createMachineWithDisableExtenionOperationsAndHasExtension(), 249 wantErr: true, 250 }, 251 } 252 for _, tc := range tests { 253 t.Run(tc.name, func(t *testing.T) { 254 g := NewWithT(t) 255 mw := &azureMachineWebhook{} 256 _, err := mw.ValidateCreate(context.Background(), tc.machine) 257 if tc.wantErr { 258 g.Expect(err).To(HaveOccurred()) 259 } else { 260 g.Expect(err).NotTo(HaveOccurred()) 261 } 262 }) 263 } 264 } 265 266 func TestAzureMachine_ValidateUpdate(t *testing.T) { 267 tests := []struct { 268 name string 269 oldMachine *AzureMachine 270 newMachine *AzureMachine 271 wantErr bool 272 }{ 273 { 274 name: "invalidTest: azuremachine.spec.image is immutable", 275 oldMachine: &AzureMachine{ 276 Spec: AzureMachineSpec{ 277 Image: &Image{ 278 ID: ptr.To("imageID-1"), 279 }, 280 }, 281 }, 282 newMachine: &AzureMachine{ 283 Spec: AzureMachineSpec{ 284 Image: &Image{ 285 ID: ptr.To("imageID-2"), 286 }, 287 }, 288 }, 289 wantErr: true, 290 }, 291 { 292 name: "validTest: azuremachine.spec.image is immutable", 293 oldMachine: &AzureMachine{ 294 Spec: AzureMachineSpec{ 295 Image: &Image{ 296 ID: ptr.To("imageID-1"), 297 }, 298 }, 299 }, 300 newMachine: &AzureMachine{ 301 Spec: AzureMachineSpec{ 302 Image: &Image{ 303 ID: ptr.To("imageID-1"), 304 }, 305 }, 306 }, 307 wantErr: false, 308 }, 309 { 310 name: "invalidTest: azuremachine.spec.Identity is immutable", 311 oldMachine: &AzureMachine{ 312 Spec: AzureMachineSpec{ 313 Identity: VMIdentityUserAssigned, 314 }, 315 }, 316 newMachine: &AzureMachine{ 317 Spec: AzureMachineSpec{ 318 Identity: VMIdentityNone, 319 }, 320 }, 321 wantErr: true, 322 }, 323 { 324 name: "validTest: azuremachine.spec.Identity is immutable", 325 oldMachine: &AzureMachine{ 326 Spec: AzureMachineSpec{ 327 Identity: VMIdentityNone, 328 }, 329 }, 330 newMachine: &AzureMachine{ 331 Spec: AzureMachineSpec{ 332 Identity: VMIdentityNone, 333 }, 334 }, 335 wantErr: false, 336 }, 337 { 338 name: "invalidTest: azuremachine.spec.UserAssignedIdentities is immutable", 339 oldMachine: &AzureMachine{ 340 Spec: AzureMachineSpec{ 341 UserAssignedIdentities: []UserAssignedIdentity{ 342 {ProviderID: "providerID-1"}, 343 }, 344 }, 345 }, 346 newMachine: &AzureMachine{ 347 Spec: AzureMachineSpec{ 348 UserAssignedIdentities: []UserAssignedIdentity{ 349 {ProviderID: "providerID-2"}, 350 }, 351 }, 352 }, 353 wantErr: true, 354 }, 355 { 356 name: "validTest: azuremachine.spec.UserAssignedIdentities is immutable", 357 oldMachine: &AzureMachine{ 358 Spec: AzureMachineSpec{ 359 UserAssignedIdentities: []UserAssignedIdentity{ 360 {ProviderID: "providerID-1"}, 361 }, 362 }, 363 }, 364 newMachine: &AzureMachine{ 365 Spec: AzureMachineSpec{ 366 UserAssignedIdentities: []UserAssignedIdentity{ 367 {ProviderID: "providerID-1"}, 368 }, 369 }, 370 }, 371 wantErr: false, 372 }, 373 { 374 name: "invalidTest: azuremachine.spec.RoleAssignmentName is immutable", 375 oldMachine: &AzureMachine{ 376 Spec: AzureMachineSpec{ 377 RoleAssignmentName: "role", 378 }, 379 }, 380 newMachine: &AzureMachine{ 381 Spec: AzureMachineSpec{ 382 RoleAssignmentName: "not-role", 383 }, 384 }, 385 wantErr: true, 386 }, 387 { 388 name: "validTest: azuremachine.spec.RoleAssignmentName is immutable", 389 oldMachine: &AzureMachine{ 390 Spec: AzureMachineSpec{ 391 RoleAssignmentName: "role", 392 }, 393 }, 394 newMachine: &AzureMachine{ 395 Spec: AzureMachineSpec{ 396 RoleAssignmentName: "role", 397 }, 398 }, 399 wantErr: false, 400 }, 401 { 402 name: "invalidTest: azuremachine.spec.RoleAssignmentName is immutable", 403 oldMachine: &AzureMachine{ 404 Spec: AzureMachineSpec{ 405 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 406 Name: "role", 407 Scope: "scope", 408 DefinitionID: "definitionID", 409 }, 410 }, 411 }, 412 newMachine: &AzureMachine{ 413 Spec: AzureMachineSpec{ 414 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 415 Name: "not-role", 416 Scope: "scope", 417 DefinitionID: "definitionID", 418 }, 419 }, 420 }, 421 wantErr: true, 422 }, 423 { 424 name: "validTest: azuremachine.spec.SystemAssignedIdentityRole is immutable", 425 oldMachine: &AzureMachine{ 426 Spec: AzureMachineSpec{ 427 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 428 Name: "role", 429 Scope: "scope", 430 DefinitionID: "definitionID", 431 }, 432 }, 433 }, 434 newMachine: &AzureMachine{ 435 Spec: AzureMachineSpec{ 436 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 437 Name: "role", 438 Scope: "scope", 439 DefinitionID: "definitionID", 440 }, 441 }, 442 }, 443 wantErr: false, 444 }, 445 { 446 name: "invalidTest: azuremachine.spec.OSDisk is immutable", 447 oldMachine: &AzureMachine{ 448 Spec: AzureMachineSpec{ 449 OSDisk: OSDisk{ 450 OSType: "osType-0", 451 }, 452 }, 453 }, 454 newMachine: &AzureMachine{ 455 Spec: AzureMachineSpec{ 456 OSDisk: OSDisk{ 457 OSType: "osType-1", 458 }, 459 }, 460 }, 461 wantErr: true, 462 }, 463 { 464 name: "validTest: azuremachine.spec.OSDisk is immutable", 465 oldMachine: &AzureMachine{ 466 Spec: AzureMachineSpec{ 467 OSDisk: OSDisk{ 468 OSType: "osType-1", 469 }, 470 }, 471 }, 472 newMachine: &AzureMachine{ 473 Spec: AzureMachineSpec{ 474 OSDisk: OSDisk{ 475 OSType: "osType-1", 476 }, 477 }, 478 }, 479 wantErr: false, 480 }, 481 { 482 name: "invalidTest: azuremachine.spec.DataDisks is immutable", 483 oldMachine: &AzureMachine{ 484 Spec: AzureMachineSpec{ 485 DataDisks: []DataDisk{ 486 { 487 DiskSizeGB: 128, 488 }, 489 }, 490 }, 491 }, 492 newMachine: &AzureMachine{ 493 Spec: AzureMachineSpec{ 494 DataDisks: []DataDisk{ 495 { 496 DiskSizeGB: 64, 497 }, 498 }, 499 }, 500 }, 501 wantErr: true, 502 }, 503 { 504 name: "validTest: azuremachine.spec.DataDisks is immutable", 505 oldMachine: &AzureMachine{ 506 Spec: AzureMachineSpec{ 507 DataDisks: []DataDisk{ 508 { 509 DiskSizeGB: 128, 510 }, 511 }, 512 }, 513 }, 514 newMachine: &AzureMachine{ 515 Spec: AzureMachineSpec{ 516 DataDisks: []DataDisk{ 517 { 518 DiskSizeGB: 128, 519 }, 520 }, 521 }, 522 }, 523 wantErr: false, 524 }, 525 { 526 name: "invalidTest: azuremachine.spec.SSHPublicKey is immutable", 527 oldMachine: &AzureMachine{ 528 Spec: AzureMachineSpec{ 529 SSHPublicKey: "validKey", 530 }, 531 }, 532 newMachine: &AzureMachine{ 533 Spec: AzureMachineSpec{ 534 SSHPublicKey: "invalidKey", 535 }, 536 }, 537 wantErr: true, 538 }, 539 { 540 name: "validTest: azuremachine.spec.SSHPublicKey is immutable", 541 oldMachine: &AzureMachine{ 542 Spec: AzureMachineSpec{ 543 SSHPublicKey: "validKey", 544 }, 545 }, 546 newMachine: &AzureMachine{ 547 Spec: AzureMachineSpec{ 548 SSHPublicKey: "validKey", 549 }, 550 }, 551 wantErr: false, 552 }, 553 { 554 name: "invalidTest: azuremachine.spec.AllocatePublicIP is immutable", 555 oldMachine: &AzureMachine{ 556 Spec: AzureMachineSpec{ 557 AllocatePublicIP: true, 558 }, 559 }, 560 newMachine: &AzureMachine{ 561 Spec: AzureMachineSpec{ 562 AllocatePublicIP: false, 563 }, 564 }, 565 wantErr: true, 566 }, 567 { 568 name: "validTest: azuremachine.spec.AllocatePublicIP is immutable", 569 oldMachine: &AzureMachine{ 570 Spec: AzureMachineSpec{ 571 AllocatePublicIP: true, 572 }, 573 }, 574 newMachine: &AzureMachine{ 575 Spec: AzureMachineSpec{ 576 AllocatePublicIP: true, 577 }, 578 }, 579 wantErr: false, 580 }, 581 { 582 name: "invalidTest: azuremachine.spec.EnableIPForwarding is immutable", 583 oldMachine: &AzureMachine{ 584 Spec: AzureMachineSpec{ 585 EnableIPForwarding: true, 586 }, 587 }, 588 newMachine: &AzureMachine{ 589 Spec: AzureMachineSpec{ 590 EnableIPForwarding: false, 591 }, 592 }, 593 wantErr: true, 594 }, 595 { 596 name: "validTest: azuremachine.spec.EnableIPForwarding is immutable", 597 oldMachine: &AzureMachine{ 598 Spec: AzureMachineSpec{ 599 EnableIPForwarding: true, 600 }, 601 }, 602 newMachine: &AzureMachine{ 603 Spec: AzureMachineSpec{ 604 EnableIPForwarding: true, 605 }, 606 }, 607 wantErr: false, 608 }, 609 { 610 name: "invalidTest: azuremachine.spec.AcceleratedNetworking is immutable", 611 oldMachine: &AzureMachine{ 612 Spec: AzureMachineSpec{ 613 AcceleratedNetworking: ptr.To(true), 614 }, 615 }, 616 newMachine: &AzureMachine{ 617 Spec: AzureMachineSpec{ 618 AcceleratedNetworking: ptr.To(false), 619 }, 620 }, 621 wantErr: true, 622 }, 623 { 624 name: "validTest: azuremachine.spec.AcceleratedNetworking is immutable", 625 oldMachine: &AzureMachine{ 626 Spec: AzureMachineSpec{ 627 AcceleratedNetworking: ptr.To(true), 628 }, 629 }, 630 newMachine: &AzureMachine{ 631 Spec: AzureMachineSpec{ 632 AcceleratedNetworking: ptr.To(true), 633 }, 634 }, 635 wantErr: false, 636 }, 637 { 638 name: "validTest: azuremachine.spec.AcceleratedNetworking transition(from true) to nil is acceptable", 639 oldMachine: &AzureMachine{ 640 Spec: AzureMachineSpec{ 641 AcceleratedNetworking: ptr.To(true), 642 }, 643 }, 644 newMachine: &AzureMachine{ 645 Spec: AzureMachineSpec{ 646 AcceleratedNetworking: nil, 647 }, 648 }, 649 wantErr: false, 650 }, 651 { 652 name: "validTest: azuremachine.spec.AcceleratedNetworking transition(from false) to nil is acceptable", 653 oldMachine: &AzureMachine{ 654 Spec: AzureMachineSpec{ 655 AcceleratedNetworking: ptr.To(false), 656 }, 657 }, 658 newMachine: &AzureMachine{ 659 Spec: AzureMachineSpec{ 660 AcceleratedNetworking: nil, 661 }, 662 }, 663 wantErr: false, 664 }, 665 { 666 name: "invalidTest: azuremachine.spec.SpotVMOptions is immutable", 667 oldMachine: &AzureMachine{ 668 Spec: AzureMachineSpec{ 669 SpotVMOptions: &SpotVMOptions{ 670 MaxPrice: &resource.Quantity{Format: "vmoptions-0"}, 671 }, 672 }, 673 }, 674 newMachine: &AzureMachine{ 675 Spec: AzureMachineSpec{ 676 SpotVMOptions: &SpotVMOptions{ 677 MaxPrice: &resource.Quantity{Format: "vmoptions-1"}, 678 }, 679 }, 680 }, 681 wantErr: true, 682 }, 683 { 684 name: "validTest: azuremachine.spec.SpotVMOptions is immutable", 685 oldMachine: &AzureMachine{ 686 Spec: AzureMachineSpec{ 687 SpotVMOptions: &SpotVMOptions{ 688 MaxPrice: &resource.Quantity{Format: "vmoptions-1"}, 689 }, 690 }, 691 }, 692 newMachine: &AzureMachine{ 693 Spec: AzureMachineSpec{ 694 SpotVMOptions: &SpotVMOptions{ 695 MaxPrice: &resource.Quantity{Format: "vmoptions-1"}, 696 }, 697 }, 698 }, 699 wantErr: false, 700 }, 701 { 702 name: "invalidTest: azuremachine.spec.SecurityProfile is immutable", 703 oldMachine: &AzureMachine{ 704 Spec: AzureMachineSpec{ 705 SecurityProfile: &SecurityProfile{EncryptionAtHost: ptr.To(true)}, 706 }, 707 }, 708 newMachine: &AzureMachine{ 709 Spec: AzureMachineSpec{ 710 SecurityProfile: &SecurityProfile{EncryptionAtHost: ptr.To(false)}, 711 }, 712 }, 713 wantErr: true, 714 }, 715 { 716 name: "validTest: azuremachine.spec.SecurityProfile is immutable", 717 oldMachine: &AzureMachine{ 718 Spec: AzureMachineSpec{ 719 SecurityProfile: &SecurityProfile{EncryptionAtHost: ptr.To(true)}, 720 }, 721 }, 722 newMachine: &AzureMachine{ 723 Spec: AzureMachineSpec{ 724 SecurityProfile: &SecurityProfile{EncryptionAtHost: ptr.To(true)}, 725 }, 726 }, 727 wantErr: false, 728 }, 729 { 730 name: "invalidTest: azuremachine.spec.Diagnostics is immutable", 731 oldMachine: &AzureMachine{ 732 Spec: AzureMachineSpec{ 733 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: DisabledDiagnosticsStorage}}, 734 }, 735 }, 736 newMachine: &AzureMachine{ 737 Spec: AzureMachineSpec{ 738 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: ManagedDiagnosticsStorage}}, 739 }, 740 }, 741 wantErr: true, 742 }, 743 { 744 name: "validTest: azuremachine.spec.Diagnostics is immutable", 745 oldMachine: &AzureMachine{ 746 Spec: AzureMachineSpec{ 747 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: DisabledDiagnosticsStorage}}, 748 }, 749 }, 750 newMachine: &AzureMachine{ 751 Spec: AzureMachineSpec{ 752 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: DisabledDiagnosticsStorage}}, 753 }, 754 }, 755 wantErr: false, 756 }, 757 { 758 name: "validTest: azuremachine.spec.Diagnostics should not error on updating nil diagnostics", 759 oldMachine: &AzureMachine{ 760 Spec: AzureMachineSpec{}, 761 }, 762 newMachine: &AzureMachine{ 763 Spec: AzureMachineSpec{ 764 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: ManagedDiagnosticsStorage}}, 765 }, 766 }, 767 wantErr: false, 768 }, 769 { 770 name: "invalidTest: azuremachine.spec.Diagnostics is immutable", 771 oldMachine: &AzureMachine{ 772 Spec: AzureMachineSpec{ 773 Diagnostics: &Diagnostics{}, 774 }, 775 }, 776 newMachine: &AzureMachine{ 777 Spec: AzureMachineSpec{ 778 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: ManagedDiagnosticsStorage}}, 779 }, 780 }, 781 wantErr: true, 782 }, 783 { 784 name: "invalidTest: azuremachine.spec.Diagnostics is immutable", 785 oldMachine: &AzureMachine{ 786 Spec: AzureMachineSpec{ 787 Diagnostics: &Diagnostics{ 788 Boot: &BootDiagnostics{}, 789 }, 790 }, 791 }, 792 newMachine: &AzureMachine{ 793 Spec: AzureMachineSpec{ 794 Diagnostics: &Diagnostics{Boot: &BootDiagnostics{StorageAccountType: ManagedDiagnosticsStorage}}, 795 }, 796 }, 797 wantErr: true, 798 }, 799 { 800 name: "invalidTest: azuremachine.spec.disableExtensionOperations is immutable", 801 oldMachine: &AzureMachine{ 802 Spec: AzureMachineSpec{ 803 DisableExtensionOperations: ptr.To(true), 804 }, 805 }, 806 newMachine: &AzureMachine{ 807 Spec: AzureMachineSpec{ 808 DisableExtensionOperations: ptr.To(false), 809 }, 810 }, 811 wantErr: true, 812 }, 813 { 814 name: "validTest: azuremachine.spec.disableExtensionOperations is immutable", 815 oldMachine: &AzureMachine{ 816 Spec: AzureMachineSpec{ 817 DisableExtensionOperations: ptr.To(true), 818 }, 819 }, 820 newMachine: &AzureMachine{ 821 Spec: AzureMachineSpec{ 822 DisableExtensionOperations: ptr.To(true), 823 }, 824 }, 825 wantErr: false, 826 }, 827 { 828 name: "validTest: azuremachine.spec.networkInterfaces is immutable", 829 oldMachine: &AzureMachine{ 830 Spec: AzureMachineSpec{ 831 NetworkInterfaces: []NetworkInterface{{SubnetName: "subnet"}}, 832 }, 833 }, 834 newMachine: &AzureMachine{ 835 Spec: AzureMachineSpec{ 836 NetworkInterfaces: []NetworkInterface{{SubnetName: "subnet"}}, 837 }, 838 }, 839 wantErr: false, 840 }, 841 { 842 name: "invalidtest: azuremachine.spec.networkInterfaces is immutable", 843 oldMachine: &AzureMachine{ 844 Spec: AzureMachineSpec{ 845 NetworkInterfaces: []NetworkInterface{{SubnetName: "subnet1"}}, 846 }, 847 }, 848 newMachine: &AzureMachine{ 849 Spec: AzureMachineSpec{ 850 NetworkInterfaces: []NetworkInterface{{SubnetName: "subnet2"}}, 851 }, 852 }, 853 wantErr: true, 854 }, 855 { 856 name: "invalidtest: updating subnet name from empty to non empty", 857 oldMachine: &AzureMachine{ 858 Spec: AzureMachineSpec{ 859 NetworkInterfaces: []NetworkInterface{{SubnetName: ""}}, 860 }, 861 }, 862 newMachine: &AzureMachine{ 863 Spec: AzureMachineSpec{ 864 NetworkInterfaces: []NetworkInterface{{SubnetName: "subnet1"}}, 865 }, 866 }, 867 wantErr: true, 868 }, 869 { 870 name: "invalidTest: azuremachine.spec.capacityReservationGroupID is immutable", 871 oldMachine: &AzureMachine{ 872 Spec: AzureMachineSpec{ 873 CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"), 874 }, 875 }, 876 newMachine: &AzureMachine{ 877 Spec: AzureMachineSpec{ 878 CapacityReservationGroupID: ptr.To("capacityReservationGroupID-2"), 879 }, 880 }, 881 wantErr: true, 882 }, 883 { 884 name: "invalidTest: updating azuremachine.spec.capacityReservationGroupID from empty to non-empty", 885 oldMachine: &AzureMachine{ 886 Spec: AzureMachineSpec{ 887 CapacityReservationGroupID: nil, 888 }, 889 }, 890 newMachine: &AzureMachine{ 891 Spec: AzureMachineSpec{ 892 CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"), 893 }, 894 }, 895 wantErr: true, 896 }, 897 { 898 name: "invalidTest: updating azuremachine.spec.capacityReservationGroupID from non-empty to empty", 899 oldMachine: &AzureMachine{ 900 Spec: AzureMachineSpec{ 901 CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"), 902 }, 903 }, 904 newMachine: &AzureMachine{ 905 Spec: AzureMachineSpec{ 906 CapacityReservationGroupID: nil, 907 }, 908 }, 909 wantErr: true, 910 }, 911 { 912 name: "validTest: azuremachine.spec.capacityReservationGroupID is immutable", 913 oldMachine: &AzureMachine{ 914 Spec: AzureMachineSpec{ 915 CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"), 916 }, 917 }, 918 newMachine: &AzureMachine{ 919 Spec: AzureMachineSpec{ 920 CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"), 921 }, 922 }, 923 wantErr: false, 924 }, 925 } 926 927 for _, tc := range tests { 928 t.Run(tc.name, func(t *testing.T) { 929 g := NewWithT(t) 930 mw := &azureMachineWebhook{} 931 _, err := mw.ValidateUpdate(context.Background(), tc.oldMachine, tc.newMachine) 932 if tc.wantErr { 933 g.Expect(err).To(HaveOccurred()) 934 } else { 935 g.Expect(err).NotTo(HaveOccurred()) 936 } 937 }) 938 } 939 } 940 941 type mockDefaultClient struct { 942 client.Client 943 SubscriptionID string 944 } 945 946 func (m mockDefaultClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { 947 switch obj := obj.(type) { 948 case *AzureCluster: 949 obj.Spec.SubscriptionID = m.SubscriptionID 950 case *clusterv1.Cluster: 951 obj.Spec.InfrastructureRef = &corev1.ObjectReference{ 952 Kind: AzureClusterKind, 953 Name: "test-cluster", 954 } 955 default: 956 return errors.New("invalid object type") 957 } 958 return nil 959 } 960 961 func TestAzureMachine_Default(t *testing.T) { 962 g := NewWithT(t) 963 964 type test struct { 965 machine *AzureMachine 966 } 967 968 testSubscriptionID := "test-subscription-id" 969 mockClient := mockDefaultClient{SubscriptionID: testSubscriptionID} 970 existingPublicKey := validSSHPublicKey 971 publicKeyExistTest := test{machine: createMachineWithSSHPublicKey(existingPublicKey)} 972 publicKeyNotExistTest := test{machine: createMachineWithSSHPublicKey("")} 973 testObjectMeta := metav1.ObjectMeta{ 974 Labels: map[string]string{ 975 clusterv1.ClusterNameLabel: "test-cluster", 976 }, 977 } 978 979 mw := &azureMachineWebhook{ 980 Client: mockClient, 981 } 982 983 err := mw.Default(context.Background(), publicKeyExistTest.machine) 984 g.Expect(err).NotTo(HaveOccurred()) 985 g.Expect(publicKeyExistTest.machine.Spec.SSHPublicKey).To(Equal(existingPublicKey)) 986 987 err = mw.Default(context.Background(), publicKeyNotExistTest.machine) 988 g.Expect(err).NotTo(HaveOccurred()) 989 g.Expect(publicKeyNotExistTest.machine.Spec.SSHPublicKey).To(Not(BeEmpty())) 990 991 cacheTypeNotSpecifiedTest := test{machine: &AzureMachine{ObjectMeta: testObjectMeta, Spec: AzureMachineSpec{OSDisk: OSDisk{CachingType: ""}}}} 992 err = mw.Default(context.Background(), cacheTypeNotSpecifiedTest.machine) 993 g.Expect(err).NotTo(HaveOccurred()) 994 g.Expect(cacheTypeNotSpecifiedTest.machine.Spec.OSDisk.CachingType).To(Equal("None")) 995 996 for _, possibleCachingType := range armcompute.PossibleCachingTypesValues() { 997 cacheTypeSpecifiedTest := test{machine: &AzureMachine{ObjectMeta: testObjectMeta, Spec: AzureMachineSpec{OSDisk: OSDisk{CachingType: string(possibleCachingType)}}}} 998 err = mw.Default(context.Background(), cacheTypeSpecifiedTest.machine) 999 g.Expect(err).NotTo(HaveOccurred()) 1000 g.Expect(cacheTypeSpecifiedTest.machine.Spec.OSDisk.CachingType).To(Equal(string(possibleCachingType))) 1001 } 1002 } 1003 1004 func createMachineWithNetworkConfig(subnetName string, acceleratedNetworking *bool, interfaces []NetworkInterface) *AzureMachine { 1005 return &AzureMachine{ 1006 Spec: AzureMachineSpec{ 1007 SubnetName: subnetName, 1008 NetworkInterfaces: interfaces, 1009 AcceleratedNetworking: acceleratedNetworking, 1010 OSDisk: validOSDisk, 1011 SSHPublicKey: validSSHPublicKey, 1012 }, 1013 } 1014 } 1015 1016 func createMachineWithSharedImage(subscriptionID, resourceGroup, name, gallery, version string) *AzureMachine { 1017 image := &Image{ 1018 SharedGallery: &AzureSharedGalleryImage{ 1019 SubscriptionID: subscriptionID, 1020 ResourceGroup: resourceGroup, 1021 Name: name, 1022 Gallery: gallery, 1023 Version: version, 1024 }, 1025 } 1026 1027 return &AzureMachine{ 1028 Spec: AzureMachineSpec{ 1029 Image: image, 1030 SSHPublicKey: validSSHPublicKey, 1031 OSDisk: validOSDisk, 1032 }, 1033 } 1034 } 1035 1036 func createMachineWithMarketPlaceImage(publisher, offer, sku, version string) *AzureMachine { 1037 image := &Image{ 1038 Marketplace: &AzureMarketplaceImage{ 1039 ImagePlan: ImagePlan{ 1040 Publisher: publisher, 1041 Offer: offer, 1042 SKU: sku, 1043 }, 1044 Version: version, 1045 }, 1046 } 1047 1048 return &AzureMachine{ 1049 Spec: AzureMachineSpec{ 1050 Image: image, 1051 SSHPublicKey: validSSHPublicKey, 1052 OSDisk: validOSDisk, 1053 }, 1054 } 1055 } 1056 1057 func createMachineWithImageByID(imageID string) *AzureMachine { 1058 image := &Image{ 1059 ID: &imageID, 1060 } 1061 1062 return &AzureMachine{ 1063 Spec: AzureMachineSpec{ 1064 Image: image, 1065 SSHPublicKey: validSSHPublicKey, 1066 OSDisk: validOSDisk, 1067 }, 1068 } 1069 } 1070 1071 func createMachineWithOsDiskCacheType(cacheType string) *AzureMachine { 1072 machine := &AzureMachine{ 1073 Spec: AzureMachineSpec{ 1074 SSHPublicKey: validSSHPublicKey, 1075 OSDisk: validOSDisk, 1076 }, 1077 } 1078 machine.Spec.OSDisk.CachingType = cacheType 1079 return machine 1080 } 1081 1082 func createMachineWithSystemAssignedIdentityRoleName() *AzureMachine { 1083 machine := &AzureMachine{ 1084 Spec: AzureMachineSpec{ 1085 SSHPublicKey: validSSHPublicKey, 1086 OSDisk: validOSDisk, 1087 Identity: VMIdentitySystemAssigned, 1088 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 1089 Name: "c6e3443d-bc11-4335-8819-ab6637b10586", 1090 Scope: "test-scope", 1091 DefinitionID: "test-definition-id", 1092 }, 1093 }, 1094 } 1095 return machine 1096 } 1097 1098 func createMachineWithoutSystemAssignedIdentityRoleName() *AzureMachine { 1099 machine := &AzureMachine{ 1100 Spec: AzureMachineSpec{ 1101 SSHPublicKey: validSSHPublicKey, 1102 OSDisk: validOSDisk, 1103 Identity: VMIdentitySystemAssigned, 1104 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 1105 Scope: "test-scope", 1106 DefinitionID: "test-definition-id", 1107 }, 1108 }, 1109 } 1110 return machine 1111 } 1112 1113 func createMachineWithoutRoleAssignmentName() *AzureMachine { 1114 machine := &AzureMachine{ 1115 Spec: AzureMachineSpec{ 1116 SSHPublicKey: validSSHPublicKey, 1117 OSDisk: validOSDisk, 1118 }, 1119 } 1120 return machine 1121 } 1122 1123 func createMachineWithRoleAssignmentName() *AzureMachine { 1124 machine := &AzureMachine{ 1125 Spec: AzureMachineSpec{ 1126 SSHPublicKey: validSSHPublicKey, 1127 OSDisk: validOSDisk, 1128 RoleAssignmentName: "test-role-assignment", 1129 }, 1130 } 1131 return machine 1132 } 1133 1134 func createMachineWithDiagnostics(diagnosticsType BootDiagnosticsStorageAccountType, userManaged *UserManagedBootDiagnostics) *AzureMachine { 1135 var diagnostics *Diagnostics 1136 1137 if diagnosticsType != "" { 1138 diagnostics = &Diagnostics{ 1139 Boot: &BootDiagnostics{ 1140 StorageAccountType: diagnosticsType, 1141 }, 1142 } 1143 } 1144 1145 if userManaged != nil { 1146 diagnostics.Boot.UserManaged = userManaged 1147 } 1148 1149 return &AzureMachine{ 1150 Spec: AzureMachineSpec{ 1151 SSHPublicKey: validSSHPublicKey, 1152 OSDisk: validOSDisk, 1153 Diagnostics: diagnostics, 1154 }, 1155 } 1156 } 1157 1158 func createMachineWithConfidentialCompute(securityEncryptionType SecurityEncryptionType, securityType SecurityTypes, encryptionAtHost, vTpmEnabled, secureBootEnabled bool) *AzureMachine { 1159 securityProfile := &SecurityProfile{ 1160 EncryptionAtHost: &encryptionAtHost, 1161 SecurityType: securityType, 1162 UefiSettings: &UefiSettings{ 1163 VTpmEnabled: &vTpmEnabled, 1164 SecureBootEnabled: &secureBootEnabled, 1165 }, 1166 } 1167 1168 osDisk := OSDisk{ 1169 DiskSizeGB: ptr.To[int32](30), 1170 OSType: LinuxOS, 1171 ManagedDisk: &ManagedDiskParameters{ 1172 StorageAccountType: "Premium_LRS", 1173 SecurityProfile: &VMDiskSecurityProfile{ 1174 SecurityEncryptionType: securityEncryptionType, 1175 }, 1176 }, 1177 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 1178 } 1179 1180 return &AzureMachine{ 1181 Spec: AzureMachineSpec{ 1182 SSHPublicKey: validSSHPublicKey, 1183 OSDisk: osDisk, 1184 SecurityProfile: securityProfile, 1185 }, 1186 } 1187 } 1188 1189 func createMachineWithCapacityReservaionGroupID(capacityReservationGroupID string) *AzureMachine { 1190 var strPtr *string 1191 if capacityReservationGroupID != "" { 1192 strPtr = ptr.To(capacityReservationGroupID) 1193 } 1194 1195 return &AzureMachine{ 1196 Spec: AzureMachineSpec{ 1197 SSHPublicKey: validSSHPublicKey, 1198 OSDisk: validOSDisk, 1199 CapacityReservationGroupID: strPtr, 1200 }, 1201 } 1202 } 1203 1204 func createMachineWithDisableExtenionOperationsAndHasExtension() *AzureMachine { 1205 return &AzureMachine{ 1206 Spec: AzureMachineSpec{ 1207 SSHPublicKey: validSSHPublicKey, 1208 OSDisk: validOSDisk, 1209 DisableExtensionOperations: ptr.To(true), 1210 VMExtensions: []VMExtension{{ 1211 Name: "test-extension", 1212 Publisher: "test-publiher", 1213 Version: "v0.0.1-test", 1214 }}, 1215 }, 1216 } 1217 } 1218 1219 func createMachineWithDisableExtenionOperations() *AzureMachine { 1220 return &AzureMachine{ 1221 Spec: AzureMachineSpec{ 1222 SSHPublicKey: validSSHPublicKey, 1223 OSDisk: validOSDisk, 1224 DisableExtensionOperations: ptr.To(true), 1225 }, 1226 } 1227 }