sigs.k8s.io/cluster-api-provider-azure@v1.17.0/api/v1beta1/azuremachine_validation_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 "crypto/rand" 21 "crypto/rsa" 22 "encoding/base64" 23 "fmt" 24 "testing" 25 26 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5" 27 "github.com/google/uuid" 28 . "github.com/onsi/gomega" 29 "golang.org/x/crypto/ssh" 30 "k8s.io/apimachinery/pkg/util/validation/field" 31 "k8s.io/utils/ptr" 32 ) 33 34 func TestAzureMachine_ValidateSSHKey(t *testing.T) { 35 tests := []struct { 36 name string 37 sshKey string 38 wantErr bool 39 }{ 40 { 41 name: "valid ssh key", 42 sshKey: generateSSHPublicKey(true), 43 wantErr: false, 44 }, 45 { 46 name: "invalid ssh key", 47 sshKey: "invalid ssh key", 48 wantErr: true, 49 }, 50 { 51 name: "ssh key not base64 encoded", 52 sshKey: generateSSHPublicKey(false), 53 wantErr: true, 54 }, 55 } 56 57 for _, tc := range tests { 58 t.Run(tc.name, func(t *testing.T) { 59 g := NewWithT(t) 60 err := ValidateSSHKey(tc.sshKey, field.NewPath("sshPublicKey")) 61 if tc.wantErr { 62 g.Expect(err).NotTo(BeEmpty()) 63 } else { 64 g.Expect(err).To(BeEmpty()) 65 } 66 }) 67 } 68 } 69 70 func generateSSHPublicKey(b64Enconded bool) string { 71 privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) 72 publicRsaKey, _ := ssh.NewPublicKey(&privateKey.PublicKey) 73 if b64Enconded { 74 return base64.StdEncoding.EncodeToString(ssh.MarshalAuthorizedKey(publicRsaKey)) 75 } 76 return string(ssh.MarshalAuthorizedKey(publicRsaKey)) 77 } 78 79 type osDiskTestInput struct { 80 name string 81 wantErr bool 82 osDisk OSDisk 83 } 84 85 func TestAzureMachine_ValidateOSDisk(t *testing.T) { 86 testcases := []osDiskTestInput{ 87 { 88 name: "valid os disk spec", 89 wantErr: false, 90 osDisk: generateValidOSDisk(), 91 }, 92 { 93 name: "invalid os disk cache type", 94 wantErr: true, 95 osDisk: createOSDiskWithCacheType("invalid_cache_type"), 96 }, 97 { 98 name: "valid ephemeral os disk spec", 99 wantErr: false, 100 osDisk: OSDisk{ 101 DiskSizeGB: ptr.To[int32](30), 102 CachingType: "None", 103 OSType: "blah", 104 DiffDiskSettings: &DiffDiskSettings{ 105 Option: string(armcompute.DiffDiskOptionsLocal), 106 }, 107 ManagedDisk: &ManagedDiskParameters{ 108 StorageAccountType: "Standard_LRS", 109 }, 110 }, 111 }, 112 { 113 name: "valid resourceDisk placement spec with option local", 114 wantErr: false, 115 osDisk: OSDisk{ 116 DiskSizeGB: ptr.To[int32](30), 117 CachingType: "None", 118 OSType: "blah", 119 DiffDiskSettings: &DiffDiskSettings{ 120 Option: string(armcompute.DiffDiskOptionsLocal), 121 Placement: ptr.To(DiffDiskPlacementResourceDisk), 122 }, 123 ManagedDisk: &ManagedDiskParameters{ 124 StorageAccountType: "Standard_LRS", 125 }, 126 }, 127 }, 128 { 129 name: "valid resourceDisk placement spec requires option local", 130 wantErr: true, 131 osDisk: OSDisk{ 132 DiskSizeGB: ptr.To[int32](30), 133 CachingType: "None", 134 OSType: "blah", 135 DiffDiskSettings: &DiffDiskSettings{ 136 Placement: ptr.To(DiffDiskPlacementResourceDisk), 137 }, 138 ManagedDisk: &ManagedDiskParameters{ 139 StorageAccountType: "Standard_LRS", 140 }, 141 }, 142 }, 143 { 144 name: "byoc encryption with ephemeral os disk spec", 145 wantErr: true, 146 osDisk: OSDisk{ 147 DiskSizeGB: ptr.To[int32](30), 148 CachingType: "None", 149 OSType: "blah", 150 DiffDiskSettings: &DiffDiskSettings{ 151 Option: string(armcompute.DiffDiskOptionsLocal), 152 }, 153 ManagedDisk: &ManagedDiskParameters{ 154 StorageAccountType: "Standard_LRS", 155 DiskEncryptionSet: &DiskEncryptionSetParameters{ 156 ID: "disk-encryption-set", 157 }, 158 }, 159 }, 160 }, 161 } 162 testcases = append(testcases, generateNegativeTestCases()...) 163 164 for _, test := range testcases { 165 t.Run(test.name, func(t *testing.T) { 166 g := NewWithT(t) 167 err := ValidateOSDisk(test.osDisk, field.NewPath("osDisk")) 168 if test.wantErr { 169 g.Expect(err).NotTo(BeEmpty()) 170 } else { 171 g.Expect(err).To(BeEmpty()) 172 } 173 }) 174 } 175 } 176 177 func generateNegativeTestCases() []osDiskTestInput { 178 inputs := []osDiskTestInput{} 179 testCaseName := "invalid os disk spec" 180 181 invalidDiskSpecs := []OSDisk{ 182 {}, 183 { 184 DiskSizeGB: ptr.To[int32](0), 185 OSType: "blah", 186 }, 187 { 188 DiskSizeGB: ptr.To[int32](-10), 189 OSType: "blah", 190 }, 191 { 192 DiskSizeGB: ptr.To[int32](2050), 193 OSType: "blah", 194 }, 195 { 196 DiskSizeGB: ptr.To[int32](20), 197 OSType: "", 198 }, 199 { 200 DiskSizeGB: ptr.To[int32](30), 201 OSType: "blah", 202 ManagedDisk: &ManagedDiskParameters{}, 203 }, 204 { 205 DiskSizeGB: ptr.To[int32](30), 206 OSType: "blah", 207 ManagedDisk: &ManagedDiskParameters{ 208 StorageAccountType: "", 209 }, 210 }, 211 { 212 DiskSizeGB: ptr.To[int32](30), 213 OSType: "blah", 214 ManagedDisk: &ManagedDiskParameters{ 215 StorageAccountType: "invalid_type", 216 }, 217 }, 218 { 219 DiskSizeGB: ptr.To[int32](30), 220 OSType: "blah", 221 ManagedDisk: &ManagedDiskParameters{ 222 StorageAccountType: "Premium_LRS", 223 }, 224 DiffDiskSettings: &DiffDiskSettings{ 225 Option: string(armcompute.DiffDiskOptionsLocal), 226 }, 227 }, 228 } 229 230 for i, input := range invalidDiskSpecs { 231 inputs = append(inputs, osDiskTestInput{ 232 name: fmt.Sprintf("%s-%d", testCaseName, i), 233 wantErr: true, 234 osDisk: input, 235 }) 236 } 237 238 return inputs 239 } 240 241 func generateValidOSDisk() OSDisk { 242 return OSDisk{ 243 DiskSizeGB: ptr.To[int32](30), 244 OSType: LinuxOS, 245 ManagedDisk: &ManagedDiskParameters{ 246 StorageAccountType: "Premium_LRS", 247 }, 248 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 249 } 250 } 251 252 func createOSDiskWithCacheType(cacheType string) OSDisk { 253 osDisk := generateValidOSDisk() 254 osDisk.CachingType = cacheType 255 return osDisk 256 } 257 258 func TestAzureMachine_ValidateDataDisks(t *testing.T) { 259 testcases := []struct { 260 name string 261 disks []DataDisk 262 wantErr bool 263 }{ 264 { 265 name: "valid nil data disks", 266 disks: nil, 267 wantErr: false, 268 }, 269 { 270 name: "valid empty data disks", 271 disks: []DataDisk{}, 272 wantErr: false, 273 }, 274 { 275 name: "valid disks", 276 disks: []DataDisk{ 277 { 278 NameSuffix: "my_disk", 279 DiskSizeGB: 64, 280 Lun: ptr.To[int32](0), 281 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 282 }, 283 { 284 NameSuffix: "my_other_disk", 285 DiskSizeGB: 64, 286 Lun: ptr.To[int32](1), 287 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 288 }, 289 }, 290 wantErr: false, 291 }, 292 { 293 name: "duplicate names", 294 disks: []DataDisk{ 295 { 296 NameSuffix: "disk", 297 DiskSizeGB: 64, 298 Lun: ptr.To[int32](0), 299 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 300 }, 301 { 302 NameSuffix: "disk", 303 DiskSizeGB: 64, 304 Lun: ptr.To[int32](1), 305 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 306 }, 307 }, 308 wantErr: true, 309 }, 310 { 311 name: "duplicate LUNs", 312 disks: []DataDisk{ 313 { 314 NameSuffix: "my_disk", 315 DiskSizeGB: 64, 316 Lun: ptr.To[int32](0), 317 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 318 }, 319 { 320 NameSuffix: "my_other_disk", 321 DiskSizeGB: 64, 322 Lun: ptr.To[int32](0), 323 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 324 }, 325 }, 326 wantErr: true, 327 }, 328 { 329 name: "invalid disk size", 330 disks: []DataDisk{ 331 { 332 NameSuffix: "my_disk", 333 DiskSizeGB: 0, 334 Lun: ptr.To[int32](0), 335 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 336 }, 337 }, 338 wantErr: true, 339 }, 340 { 341 name: "empty name", 342 disks: []DataDisk{ 343 { 344 NameSuffix: "", 345 DiskSizeGB: 0, 346 Lun: ptr.To[int32](0), 347 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 348 }, 349 }, 350 wantErr: true, 351 }, 352 { 353 name: "invalid disk cachingType", 354 disks: []DataDisk{ 355 { 356 NameSuffix: "my_disk", 357 DiskSizeGB: 64, 358 Lun: ptr.To[int32](0), 359 CachingType: "invalidCacheType", 360 }, 361 }, 362 wantErr: true, 363 }, 364 { 365 name: "valid disk cachingType", 366 disks: []DataDisk{ 367 { 368 NameSuffix: "my_disk", 369 DiskSizeGB: 64, 370 Lun: ptr.To[int32](0), 371 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 372 }, 373 }, 374 wantErr: false, 375 }, 376 { 377 name: "valid managed disk storage account type", 378 disks: []DataDisk{ 379 { 380 NameSuffix: "my_disk_1", 381 DiskSizeGB: 64, 382 ManagedDisk: &ManagedDiskParameters{ 383 StorageAccountType: "Premium_LRS", 384 }, 385 Lun: ptr.To[int32](0), 386 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 387 }, 388 { 389 NameSuffix: "my_disk_2", 390 DiskSizeGB: 64, 391 ManagedDisk: &ManagedDiskParameters{ 392 StorageAccountType: "Standard_LRS", 393 }, 394 Lun: ptr.To[int32](1), 395 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 396 }, 397 }, 398 wantErr: false, 399 }, 400 { 401 name: "invalid managed disk storage account type", 402 disks: []DataDisk{ 403 { 404 NameSuffix: "my_disk_1", 405 DiskSizeGB: 64, 406 ManagedDisk: &ManagedDiskParameters{ 407 StorageAccountType: "invalid storage account", 408 }, 409 Lun: ptr.To[int32](0), 410 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 411 }, 412 }, 413 wantErr: true, 414 }, 415 { 416 name: "valid combination of managed disk storage account type UltraSSD_LRS and cachingType None", 417 disks: []DataDisk{ 418 { 419 NameSuffix: "my_disk_1", 420 DiskSizeGB: 64, 421 ManagedDisk: &ManagedDiskParameters{ 422 StorageAccountType: string(armcompute.StorageAccountTypesUltraSSDLRS), 423 }, 424 Lun: ptr.To[int32](0), 425 CachingType: string(armcompute.CachingTypesNone), 426 }, 427 }, 428 wantErr: false, 429 }, 430 { 431 name: "invalid combination of managed disk storage account type UltraSSD_LRS and cachingType ReadWrite", 432 disks: []DataDisk{ 433 { 434 NameSuffix: "my_disk_1", 435 DiskSizeGB: 64, 436 ManagedDisk: &ManagedDiskParameters{ 437 StorageAccountType: string(armcompute.StorageAccountTypesUltraSSDLRS), 438 }, 439 Lun: ptr.To[int32](0), 440 CachingType: string(armcompute.CachingTypesReadWrite), 441 }, 442 }, 443 wantErr: true, 444 }, 445 { 446 name: "invalid combination of managed disk storage account type UltraSSD_LRS and cachingType ReadOnly", 447 disks: []DataDisk{ 448 { 449 NameSuffix: "my_disk_1", 450 DiskSizeGB: 64, 451 ManagedDisk: &ManagedDiskParameters{ 452 StorageAccountType: string(armcompute.StorageAccountTypesUltraSSDLRS), 453 }, 454 Lun: ptr.To[int32](0), 455 CachingType: string(armcompute.CachingTypesReadOnly), 456 }, 457 }, 458 wantErr: true, 459 }, 460 } 461 462 for _, test := range testcases { 463 t.Run(test.name, func(t *testing.T) { 464 g := NewWithT(t) 465 err := ValidateDataDisks(test.disks, field.NewPath("dataDisks")) 466 if test.wantErr { 467 g.Expect(err).NotTo(BeEmpty()) 468 } else { 469 g.Expect(err).To(BeEmpty()) 470 } 471 }) 472 } 473 } 474 475 func TestAzureMachine_ValidateSystemAssignedIdentity(t *testing.T) { 476 tests := []struct { 477 name string 478 roleAssignmentName string 479 old string 480 Identity VMIdentity 481 wantErr bool 482 }{ 483 { 484 name: "valid UUID", 485 roleAssignmentName: uuid.New().String(), 486 Identity: VMIdentitySystemAssigned, 487 wantErr: false, 488 }, 489 { 490 name: "wrong Identity type", 491 roleAssignmentName: uuid.New().String(), 492 Identity: VMIdentityNone, 493 wantErr: true, 494 }, 495 { 496 name: "not a valid UUID", 497 roleAssignmentName: "notaguid", 498 Identity: VMIdentitySystemAssigned, 499 wantErr: true, 500 }, 501 { 502 name: "empty", 503 roleAssignmentName: "", 504 Identity: VMIdentitySystemAssigned, 505 wantErr: true, 506 }, 507 { 508 name: "changed", 509 roleAssignmentName: uuid.New().String(), 510 old: uuid.New().String(), 511 Identity: VMIdentitySystemAssigned, 512 wantErr: true, 513 }, 514 } 515 516 for _, tc := range tests { 517 t.Run(tc.name, func(t *testing.T) { 518 g := NewWithT(t) 519 err := ValidateSystemAssignedIdentity(tc.Identity, tc.old, tc.roleAssignmentName, field.NewPath("sshPublicKey")) 520 if tc.wantErr { 521 g.Expect(err).NotTo(BeEmpty()) 522 } else { 523 g.Expect(err).To(BeEmpty()) 524 } 525 }) 526 } 527 } 528 529 func TestAzureMachine_ValidateSystemAssignedIdentityRole(t *testing.T) { 530 tests := []struct { 531 name string 532 Identity VMIdentity 533 roleAssignmentName string 534 role *SystemAssignedIdentityRole 535 wantErr bool 536 }{ 537 { 538 name: "valid role", 539 Identity: VMIdentitySystemAssigned, 540 role: &SystemAssignedIdentityRole{ 541 Name: uuid.New().String(), 542 Scope: "fake-scope", 543 DefinitionID: "fake-definition-id", 544 }, 545 }, 546 { 547 name: "valid role using deprecated role assignment name", 548 Identity: VMIdentitySystemAssigned, 549 roleAssignmentName: uuid.New().String(), 550 role: &SystemAssignedIdentityRole{ 551 Scope: "fake-scope", 552 DefinitionID: "fake-definition-id", 553 }, 554 }, 555 { 556 name: "set both system assigned identity role and role assignment name", 557 Identity: VMIdentitySystemAssigned, 558 roleAssignmentName: uuid.New().String(), 559 role: &SystemAssignedIdentityRole{ 560 Name: uuid.New().String(), 561 Scope: "fake-scope", 562 DefinitionID: "fake-definition-id", 563 }, 564 wantErr: true, 565 }, 566 { 567 name: "wrong Identity type", 568 Identity: VMIdentityUserAssigned, 569 role: &SystemAssignedIdentityRole{ 570 Name: uuid.New().String(), 571 Scope: "fake-scope", 572 DefinitionID: "fake-definition-id", 573 }, 574 wantErr: true, 575 }, 576 { 577 name: "missing scope", 578 Identity: VMIdentitySystemAssigned, 579 role: &SystemAssignedIdentityRole{ 580 Name: uuid.New().String(), 581 DefinitionID: "fake-definition-id", 582 }, 583 wantErr: true, 584 }, 585 { 586 name: "missing definition id", 587 Identity: VMIdentitySystemAssigned, 588 role: &SystemAssignedIdentityRole{ 589 Name: uuid.New().String(), 590 Scope: "fake-scope", 591 }, 592 wantErr: true, 593 }, 594 } 595 596 for _, tc := range tests { 597 t.Run(tc.name, func(t *testing.T) { 598 g := NewWithT(t) 599 err := ValidateSystemAssignedIdentityRole(tc.Identity, tc.roleAssignmentName, tc.role, field.NewPath("systemAssignedIdentityRole")) 600 if tc.wantErr { 601 g.Expect(err).NotTo(BeEmpty()) 602 } else { 603 g.Expect(err).To(BeEmpty()) 604 } 605 }) 606 } 607 } 608 609 func TestAzureMachine_ValidateUserAssignedIdentity(t *testing.T) { 610 tests := []struct { 611 name string 612 idType VMIdentity 613 identities []UserAssignedIdentity 614 wantErr bool 615 }{ 616 { 617 name: "empty identity list", 618 idType: VMIdentityUserAssigned, 619 identities: []UserAssignedIdentity{}, 620 wantErr: true, 621 }, 622 { 623 name: "invalid: providerID must start with slash", 624 idType: VMIdentityUserAssigned, 625 identities: []UserAssignedIdentity{ 626 { 627 ProviderID: "subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265", 628 }, 629 }, 630 wantErr: true, 631 }, 632 { 633 name: "invalid: providerID must start with subscriptions or providers", 634 idType: VMIdentityUserAssigned, 635 identities: []UserAssignedIdentity{ 636 { 637 ProviderID: "azure:///prescriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265", 638 }, 639 }, 640 wantErr: true, 641 }, 642 { 643 name: "valid", 644 idType: VMIdentityUserAssigned, 645 identities: []UserAssignedIdentity{ 646 { 647 ProviderID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265", 648 }, 649 }, 650 wantErr: false, 651 }, 652 { 653 name: "valid with provider prefix", 654 idType: VMIdentityUserAssigned, 655 identities: []UserAssignedIdentity{ 656 { 657 ProviderID: "azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/virtualMachines/default-20202-control-plane-7w265", 658 }, 659 }, 660 wantErr: false, 661 }, 662 } 663 664 for _, tc := range tests { 665 t.Run(tc.name, func(t *testing.T) { 666 g := NewWithT(t) 667 errs := ValidateUserAssignedIdentity(tc.idType, tc.identities, field.NewPath("userAssignedIdentities")) 668 if tc.wantErr { 669 g.Expect(errs).NotTo(BeEmpty()) 670 } else { 671 g.Expect(errs).To(BeEmpty()) 672 } 673 }) 674 } 675 } 676 677 func TestAzureMachine_ValidateDataDisksUpdate(t *testing.T) { 678 tests := []struct { 679 name string 680 disks []DataDisk 681 oldDisks []DataDisk 682 wantErr bool 683 }{ 684 { 685 name: "valid nil data disks", 686 disks: nil, 687 oldDisks: nil, 688 wantErr: false, 689 }, 690 { 691 name: "valid empty data disks", 692 disks: []DataDisk{}, 693 oldDisks: []DataDisk{}, 694 wantErr: false, 695 }, 696 { 697 name: "valid data disk updates", 698 disks: []DataDisk{ 699 { 700 NameSuffix: "my_disk", 701 DiskSizeGB: 64, 702 Lun: ptr.To[int32](0), 703 ManagedDisk: &ManagedDiskParameters{ 704 StorageAccountType: "Standard_LRS", 705 }, 706 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 707 }, 708 { 709 NameSuffix: "my_other_disk", 710 DiskSizeGB: 64, 711 Lun: ptr.To[int32](1), 712 ManagedDisk: &ManagedDiskParameters{ 713 StorageAccountType: "Standard_LRS", 714 }, 715 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 716 }, 717 }, 718 oldDisks: []DataDisk{ 719 { 720 NameSuffix: "my_disk", 721 DiskSizeGB: 64, 722 Lun: ptr.To[int32](0), 723 ManagedDisk: &ManagedDiskParameters{ 724 StorageAccountType: "Standard_LRS", 725 }, 726 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 727 }, 728 { 729 NameSuffix: "my_other_disk", 730 DiskSizeGB: 64, 731 Lun: ptr.To[int32](1), 732 ManagedDisk: &ManagedDiskParameters{ 733 StorageAccountType: "Standard_LRS", 734 }, 735 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 736 }, 737 }, 738 wantErr: false, 739 }, 740 { 741 name: "cannot update data disk fields after machine creation", 742 disks: []DataDisk{ 743 { 744 NameSuffix: "my_disk_1", 745 DiskSizeGB: 64, 746 ManagedDisk: &ManagedDiskParameters{ 747 StorageAccountType: "Standard_LRS", 748 }, 749 Lun: ptr.To[int32](0), 750 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 751 }, 752 }, 753 oldDisks: []DataDisk{ 754 { 755 NameSuffix: "my_disk_1", 756 DiskSizeGB: 128, 757 ManagedDisk: &ManagedDiskParameters{ 758 StorageAccountType: "Premium_LRS", 759 }, 760 Lun: ptr.To[int32](0), 761 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 762 }, 763 }, 764 wantErr: true, 765 }, 766 { 767 name: "validate updates to optional fields", 768 disks: []DataDisk{ 769 { 770 NameSuffix: "my_disk_1", 771 DiskSizeGB: 128, 772 ManagedDisk: &ManagedDiskParameters{ 773 StorageAccountType: "Standard_LRS", 774 }, 775 Lun: ptr.To[int32](0), 776 }, 777 }, 778 oldDisks: []DataDisk{ 779 { 780 NameSuffix: "my_disk_1", 781 DiskSizeGB: 128, 782 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 783 }, 784 }, 785 wantErr: true, 786 }, 787 { 788 name: "data disks cannot be added after machine creation", 789 disks: []DataDisk{ 790 { 791 NameSuffix: "my_disk_1", 792 DiskSizeGB: 64, 793 ManagedDisk: &ManagedDiskParameters{ 794 StorageAccountType: "Standard_LRS", 795 }, 796 Lun: ptr.To[int32](0), 797 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 798 }, 799 }, 800 oldDisks: []DataDisk{ 801 { 802 NameSuffix: "my_disk_1", 803 DiskSizeGB: 64, 804 ManagedDisk: &ManagedDiskParameters{ 805 StorageAccountType: "Premium_LRS", 806 }, 807 Lun: ptr.To[int32](0), 808 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 809 }, 810 { 811 NameSuffix: "my_disk_2", 812 DiskSizeGB: 64, 813 ManagedDisk: &ManagedDiskParameters{ 814 StorageAccountType: "Premium_LRS", 815 }, 816 Lun: ptr.To[int32](2), 817 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 818 }, 819 }, 820 wantErr: true, 821 }, 822 { 823 name: "data disks cannot be removed after machine creation", 824 disks: []DataDisk{ 825 { 826 NameSuffix: "my_disk_1", 827 DiskSizeGB: 64, 828 ManagedDisk: &ManagedDiskParameters{ 829 StorageAccountType: "Standard_LRS", 830 }, 831 Lun: ptr.To[int32](0), 832 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 833 }, 834 { 835 NameSuffix: "my_disk_2", 836 DiskSizeGB: 64, 837 ManagedDisk: &ManagedDiskParameters{ 838 StorageAccountType: "Premium_LRS", 839 }, 840 Lun: ptr.To[int32](2), 841 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 842 }, 843 }, 844 oldDisks: []DataDisk{ 845 { 846 NameSuffix: "my_disk_1", 847 DiskSizeGB: 64, 848 ManagedDisk: &ManagedDiskParameters{ 849 StorageAccountType: "Standard_LRS", 850 }, 851 Lun: ptr.To[int32](0), 852 CachingType: string(armcompute.PossibleCachingTypesValues()[0]), 853 }, 854 }, 855 wantErr: true, 856 }, 857 } 858 859 for _, test := range tests { 860 t.Run(test.name, func(t *testing.T) { 861 g := NewWithT(t) 862 err := ValidateDataDisksUpdate(test.oldDisks, test.disks, field.NewPath("dataDisks")) 863 if test.wantErr { 864 g.Expect(err).NotTo(BeEmpty()) 865 } else { 866 g.Expect(err).To(BeEmpty()) 867 } 868 }) 869 } 870 } 871 872 func TestAzureMachine_ValidateNetwork(t *testing.T) { 873 tests := []struct { 874 name string 875 subnetName string 876 acceleratedNetworking *bool 877 networkInterfaces []NetworkInterface 878 wantErr bool 879 }{ 880 { 881 name: "valid config with deprecated network fields", 882 subnetName: "subnet1", 883 acceleratedNetworking: ptr.To(true), 884 networkInterfaces: nil, 885 wantErr: false, 886 }, 887 { 888 name: "valid config with networkInterfaces fields", 889 subnetName: "", 890 acceleratedNetworking: nil, 891 networkInterfaces: []NetworkInterface{{ 892 SubnetName: "subnet1", 893 AcceleratedNetworking: ptr.To(true), 894 PrivateIPConfigs: 1, 895 }}, 896 wantErr: false, 897 }, 898 { 899 name: "valid config with multiple networkInterfaces", 900 subnetName: "", 901 acceleratedNetworking: nil, 902 networkInterfaces: []NetworkInterface{ 903 { 904 SubnetName: "subnet1", 905 AcceleratedNetworking: ptr.To(true), 906 PrivateIPConfigs: 1, 907 }, 908 { 909 SubnetName: "subnet2", 910 AcceleratedNetworking: ptr.To(true), 911 PrivateIPConfigs: 30, 912 }, 913 }, 914 wantErr: false, 915 }, 916 { 917 name: "invalid config using both deprecated subnetName and networkInterfaces", 918 subnetName: "subnet1", 919 acceleratedNetworking: nil, 920 networkInterfaces: []NetworkInterface{{ 921 SubnetName: "subnet1", 922 AcceleratedNetworking: nil, 923 PrivateIPConfigs: 1, 924 }}, 925 wantErr: true, 926 }, 927 { 928 name: "invalid config using both deprecated acceleratedNetworking and networkInterfaces", 929 subnetName: "", 930 acceleratedNetworking: ptr.To(true), 931 networkInterfaces: []NetworkInterface{{ 932 SubnetName: "subnet1", 933 AcceleratedNetworking: ptr.To(true), 934 PrivateIPConfigs: 1, 935 }}, 936 wantErr: true, 937 }, 938 { 939 name: "invalid config setting privateIPConfigs to less than 1", 940 subnetName: "", 941 acceleratedNetworking: nil, 942 networkInterfaces: []NetworkInterface{{ 943 SubnetName: "subnet1", 944 AcceleratedNetworking: ptr.To(true), 945 PrivateIPConfigs: 0, 946 }}, 947 wantErr: true, 948 }, 949 } 950 951 for _, test := range tests { 952 t.Run(test.name, func(t *testing.T) { 953 g := NewWithT(t) 954 err := ValidateNetwork(test.subnetName, test.acceleratedNetworking, test.networkInterfaces, field.NewPath("networkInterfaces")) 955 if test.wantErr { 956 g.Expect(err).NotTo(BeEmpty()) 957 } else { 958 g.Expect(err).To(BeEmpty()) 959 } 960 }) 961 } 962 } 963 964 func TestAzureMachine_ValidateConfidentialCompute(t *testing.T) { 965 tests := []struct { 966 name string 967 managedDisk *ManagedDiskParameters 968 securityProfile *SecurityProfile 969 wantErr bool 970 }{ 971 { 972 name: "valid configuration without confidential compute", 973 managedDisk: &ManagedDiskParameters{ 974 SecurityProfile: &VMDiskSecurityProfile{ 975 SecurityEncryptionType: "", 976 }, 977 }, 978 securityProfile: nil, 979 wantErr: false, 980 }, 981 { 982 name: "valid configuration without confidential compute and host encryption enabled", 983 managedDisk: &ManagedDiskParameters{ 984 SecurityProfile: &VMDiskSecurityProfile{ 985 SecurityEncryptionType: "", 986 }, 987 }, 988 securityProfile: &SecurityProfile{ 989 EncryptionAtHost: ptr.To(true), 990 }, 991 wantErr: false, 992 }, 993 { 994 name: "valid configuration with VMGuestStateOnly encryption and secure boot disabled", 995 managedDisk: &ManagedDiskParameters{ 996 SecurityProfile: &VMDiskSecurityProfile{ 997 SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly, 998 }, 999 }, 1000 securityProfile: &SecurityProfile{ 1001 SecurityType: SecurityTypesConfidentialVM, 1002 UefiSettings: &UefiSettings{ 1003 VTpmEnabled: ptr.To(true), 1004 SecureBootEnabled: ptr.To(false), 1005 }, 1006 }, 1007 wantErr: false, 1008 }, 1009 { 1010 name: "valid configuration with VMGuestStateOnly encryption and secure boot enabled", 1011 managedDisk: &ManagedDiskParameters{ 1012 SecurityProfile: &VMDiskSecurityProfile{ 1013 SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly, 1014 }, 1015 }, 1016 securityProfile: &SecurityProfile{ 1017 SecurityType: SecurityTypesConfidentialVM, 1018 UefiSettings: &UefiSettings{ 1019 VTpmEnabled: ptr.To(true), 1020 SecureBootEnabled: ptr.To(true), 1021 }, 1022 }, 1023 wantErr: false, 1024 }, 1025 { 1026 name: "valid configuration with VMGuestStateOnly encryption and EncryptionAtHost enabled", 1027 managedDisk: &ManagedDiskParameters{ 1028 SecurityProfile: &VMDiskSecurityProfile{ 1029 SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly, 1030 }, 1031 }, 1032 securityProfile: &SecurityProfile{ 1033 EncryptionAtHost: ptr.To(true), 1034 SecurityType: SecurityTypesConfidentialVM, 1035 UefiSettings: &UefiSettings{ 1036 VTpmEnabled: ptr.To(true), 1037 }, 1038 }, 1039 wantErr: false, 1040 }, 1041 { 1042 name: "valid configuration with DiskWithVMGuestState encryption", 1043 managedDisk: &ManagedDiskParameters{ 1044 SecurityProfile: &VMDiskSecurityProfile{ 1045 SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState, 1046 }, 1047 }, 1048 securityProfile: &SecurityProfile{ 1049 SecurityType: SecurityTypesConfidentialVM, 1050 UefiSettings: &UefiSettings{ 1051 SecureBootEnabled: ptr.To(true), 1052 VTpmEnabled: ptr.To(true), 1053 }, 1054 }, 1055 wantErr: false, 1056 }, 1057 { 1058 name: "invalid configuration with DiskWithVMGuestState encryption and EncryptionAtHost enabled", 1059 managedDisk: &ManagedDiskParameters{ 1060 SecurityProfile: &VMDiskSecurityProfile{ 1061 SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState, 1062 }, 1063 }, 1064 securityProfile: &SecurityProfile{ 1065 EncryptionAtHost: ptr.To(true), 1066 }, 1067 wantErr: true, 1068 }, 1069 { 1070 name: "invalid configuration with DiskWithVMGuestState encryption and vTPM disabled", 1071 managedDisk: &ManagedDiskParameters{ 1072 SecurityProfile: &VMDiskSecurityProfile{ 1073 SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState, 1074 }, 1075 }, 1076 securityProfile: &SecurityProfile{ 1077 SecurityType: SecurityTypesConfidentialVM, 1078 UefiSettings: &UefiSettings{ 1079 VTpmEnabled: ptr.To(false), 1080 SecureBootEnabled: ptr.To(false), 1081 }, 1082 }, 1083 wantErr: true, 1084 }, 1085 { 1086 name: "invalid configuration with DiskWithVMGuestState encryption and secure boot disabled", 1087 managedDisk: &ManagedDiskParameters{ 1088 SecurityProfile: &VMDiskSecurityProfile{ 1089 SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState, 1090 }, 1091 }, 1092 securityProfile: &SecurityProfile{ 1093 SecurityType: SecurityTypesConfidentialVM, 1094 UefiSettings: &UefiSettings{ 1095 VTpmEnabled: ptr.To(true), 1096 SecureBootEnabled: ptr.To(false), 1097 }, 1098 }, 1099 wantErr: true, 1100 }, 1101 { 1102 name: "invalid configuration with DiskWithVMGuestState encryption and SecurityType not set to ConfidentialVM", 1103 managedDisk: &ManagedDiskParameters{ 1104 SecurityProfile: &VMDiskSecurityProfile{ 1105 SecurityEncryptionType: SecurityEncryptionTypeDiskWithVMGuestState, 1106 }, 1107 }, 1108 securityProfile: &SecurityProfile{ 1109 UefiSettings: &UefiSettings{ 1110 VTpmEnabled: ptr.To(true), 1111 SecureBootEnabled: ptr.To(true), 1112 }, 1113 }, 1114 wantErr: true, 1115 }, 1116 { 1117 name: "invalid configuration with VMGuestStateOnly encryption and SecurityType not set to ConfidentialVM", 1118 managedDisk: &ManagedDiskParameters{ 1119 SecurityProfile: &VMDiskSecurityProfile{ 1120 SecurityEncryptionType: SecurityEncryptionTypeVMGuestStateOnly, 1121 }, 1122 }, 1123 securityProfile: &SecurityProfile{ 1124 UefiSettings: &UefiSettings{ 1125 VTpmEnabled: ptr.To(true), 1126 SecureBootEnabled: ptr.To(true), 1127 }, 1128 }, 1129 wantErr: true, 1130 }, 1131 } 1132 1133 for _, tc := range tests { 1134 t.Run(tc.name, func(t *testing.T) { 1135 g := NewWithT(t) 1136 err := ValidateConfidentialCompute(tc.managedDisk, tc.securityProfile, field.NewPath("securityProfile")) 1137 if tc.wantErr { 1138 g.Expect(err).NotTo(BeEmpty()) 1139 } else { 1140 g.Expect(err).To(BeEmpty()) 1141 } 1142 }) 1143 } 1144 }