sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azuremachine_default_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 "encoding/json" 22 "fmt" 23 "reflect" 24 "testing" 25 26 "github.com/google/uuid" 27 . "github.com/onsi/gomega" 28 "github.com/pkg/errors" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/api/resource" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/utils/ptr" 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 ) 36 37 func TestAzureMachineSpec_SetDefaultSSHPublicKey(t *testing.T) { 38 g := NewWithT(t) 39 40 type test struct { 41 machine *AzureMachine 42 } 43 44 existingPublicKey := "testpublickey" 45 publicKeyExistTest := test{machine: createMachineWithSSHPublicKey(existingPublicKey)} 46 publicKeyNotExistTest := test{machine: createMachineWithSSHPublicKey("")} 47 48 err := publicKeyExistTest.machine.Spec.SetDefaultSSHPublicKey() 49 g.Expect(err).NotTo(HaveOccurred()) 50 g.Expect(publicKeyExistTest.machine.Spec.SSHPublicKey).To(Equal(existingPublicKey)) 51 52 err = publicKeyNotExistTest.machine.Spec.SetDefaultSSHPublicKey() 53 g.Expect(err).NotTo(HaveOccurred()) 54 g.Expect(publicKeyNotExistTest.machine.Spec.SSHPublicKey).To(Not(BeEmpty())) 55 } 56 57 func TestAzureMachineSpec_SetIdentityDefaults(t *testing.T) { 58 g := NewWithT(t) 59 60 type test struct { 61 machine *AzureMachine 62 } 63 64 fakeSubscriptionID := uuid.New().String() 65 fakeClusterName := "testcluster" 66 fakeRoleDefinitionID := "testroledefinitionid" 67 fakeScope := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", fakeSubscriptionID, fakeClusterName) 68 existingRoleAssignmentName := "42862306-e485-4319-9bf0-35dbc6f6fe9c" 69 roleAssignmentExistTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 70 Identity: VMIdentitySystemAssigned, 71 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 72 Name: existingRoleAssignmentName, 73 }, 74 }}} 75 notSystemAssignedTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 76 Identity: VMIdentityUserAssigned, 77 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{}, 78 }}} 79 systemAssignedIdentityRoleExistTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 80 Identity: VMIdentitySystemAssigned, 81 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 82 Scope: fakeScope, 83 DefinitionID: fakeRoleDefinitionID, 84 }, 85 }}} 86 deprecatedRoleAssignmentNameTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 87 Identity: VMIdentitySystemAssigned, 88 RoleAssignmentName: existingRoleAssignmentName, 89 }}} 90 emptyTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 91 Identity: VMIdentitySystemAssigned, 92 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{}, 93 }}} 94 bothDeprecatedRoleAssignmentNameAndSystemAssignedIdentityRoleTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 95 Identity: VMIdentitySystemAssigned, 96 RoleAssignmentName: existingRoleAssignmentName, 97 SystemAssignedIdentityRole: &SystemAssignedIdentityRole{ 98 Name: existingRoleAssignmentName, 99 }, 100 }}} 101 102 roleAssignmentExistTest.machine.Spec.SetIdentityDefaults(fakeSubscriptionID) 103 g.Expect(roleAssignmentExistTest.machine.Spec.SystemAssignedIdentityRole.Name).To(Equal(existingRoleAssignmentName)) 104 105 notSystemAssignedTest.machine.Spec.SetIdentityDefaults(fakeSubscriptionID) 106 g.Expect(notSystemAssignedTest.machine.Spec.SystemAssignedIdentityRole.Name).To(BeEmpty()) 107 108 systemAssignedIdentityRoleExistTest.machine.Spec.SetIdentityDefaults(fakeSubscriptionID) 109 g.Expect(systemAssignedIdentityRoleExistTest.machine.Spec.SystemAssignedIdentityRole.Scope).To(Equal(fakeScope)) 110 g.Expect(systemAssignedIdentityRoleExistTest.machine.Spec.SystemAssignedIdentityRole.DefinitionID).To(Equal(fakeRoleDefinitionID)) 111 112 deprecatedRoleAssignmentNameTest.machine.Spec.SetIdentityDefaults(fakeSubscriptionID) 113 g.Expect(deprecatedRoleAssignmentNameTest.machine.Spec.SystemAssignedIdentityRole.Name).To(Equal(existingRoleAssignmentName)) 114 g.Expect(deprecatedRoleAssignmentNameTest.machine.Spec.RoleAssignmentName).To(BeEmpty()) 115 116 emptyTest.machine.Spec.SetIdentityDefaults(fakeSubscriptionID) 117 g.Expect(emptyTest.machine.Spec.SystemAssignedIdentityRole.Name).To(Not(BeEmpty())) 118 _, err := uuid.Parse(emptyTest.machine.Spec.SystemAssignedIdentityRole.Name) 119 g.Expect(err).To(Not(HaveOccurred())) 120 g.Expect(emptyTest.machine.Spec.SystemAssignedIdentityRole.Scope).To(Equal(fmt.Sprintf("/subscriptions/%s/", fakeSubscriptionID))) 121 g.Expect(emptyTest.machine.Spec.SystemAssignedIdentityRole.DefinitionID).To(Equal(fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s", fakeSubscriptionID, ContributorRoleID))) 122 123 bothDeprecatedRoleAssignmentNameAndSystemAssignedIdentityRoleTest.machine.Spec.SetIdentityDefaults(fakeSubscriptionID) 124 g.Expect(bothDeprecatedRoleAssignmentNameAndSystemAssignedIdentityRoleTest.machine.Spec.RoleAssignmentName).To(Not(BeEmpty())) 125 g.Expect(bothDeprecatedRoleAssignmentNameAndSystemAssignedIdentityRoleTest.machine.Spec.SystemAssignedIdentityRole.Name).To(Not(BeEmpty())) 126 } 127 128 func TestAzureMachineSpec_SetSpotEvictionPolicyDefaults(t *testing.T) { 129 deallocatePolicy := SpotEvictionPolicyDeallocate 130 deletePolicy := SpotEvictionPolicyDelete 131 132 g := NewWithT(t) 133 134 type test struct { 135 machine *AzureMachine 136 } 137 138 spotVMOptionsExistTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 139 SpotVMOptions: &SpotVMOptions{ 140 MaxPrice: &resource.Quantity{Format: "vmoptions-0"}, 141 }, 142 }}} 143 144 localDiffDiskSettingsExistTest := test{machine: &AzureMachine{Spec: AzureMachineSpec{ 145 SpotVMOptions: &SpotVMOptions{ 146 MaxPrice: &resource.Quantity{}, 147 }, 148 OSDisk: OSDisk{ 149 DiffDiskSettings: &DiffDiskSettings{ 150 Option: "Local", 151 }, 152 }, 153 }}} 154 155 spotVMOptionsExistTest.machine.Spec.SetSpotEvictionPolicyDefaults() 156 g.Expect(spotVMOptionsExistTest.machine.Spec.SpotVMOptions.EvictionPolicy).To(Equal(&deallocatePolicy)) 157 158 localDiffDiskSettingsExistTest.machine.Spec.SetSpotEvictionPolicyDefaults() 159 g.Expect(localDiffDiskSettingsExistTest.machine.Spec.SpotVMOptions.EvictionPolicy).To(Equal(&deletePolicy)) 160 } 161 162 func TestAzureMachineSpec_SetDataDisksDefaults(t *testing.T) { 163 cases := []struct { 164 name string 165 disks []DataDisk 166 output []DataDisk 167 }{ 168 { 169 name: "no disks", 170 disks: []DataDisk{}, 171 output: []DataDisk{}, 172 }, 173 { 174 name: "no LUNs specified", 175 disks: []DataDisk{ 176 { 177 NameSuffix: "testdisk1", 178 DiskSizeGB: 30, 179 CachingType: "ReadWrite", 180 }, 181 { 182 NameSuffix: "testdisk2", 183 DiskSizeGB: 30, 184 CachingType: "ReadWrite", 185 }, 186 }, 187 output: []DataDisk{ 188 { 189 NameSuffix: "testdisk1", 190 DiskSizeGB: 30, 191 Lun: ptr.To[int32](0), 192 CachingType: "ReadWrite", 193 }, 194 { 195 NameSuffix: "testdisk2", 196 DiskSizeGB: 30, 197 Lun: ptr.To[int32](1), 198 CachingType: "ReadWrite", 199 }, 200 }, 201 }, 202 { 203 name: "All LUNs specified", 204 disks: []DataDisk{ 205 { 206 NameSuffix: "testdisk1", 207 DiskSizeGB: 30, 208 Lun: ptr.To[int32](5), 209 CachingType: "ReadWrite", 210 }, 211 { 212 NameSuffix: "testdisk2", 213 DiskSizeGB: 30, 214 Lun: ptr.To[int32](3), 215 CachingType: "ReadWrite", 216 }, 217 }, 218 output: []DataDisk{ 219 { 220 NameSuffix: "testdisk1", 221 DiskSizeGB: 30, 222 Lun: ptr.To[int32](5), 223 CachingType: "ReadWrite", 224 }, 225 { 226 NameSuffix: "testdisk2", 227 DiskSizeGB: 30, 228 Lun: ptr.To[int32](3), 229 CachingType: "ReadWrite", 230 }, 231 }, 232 }, 233 { 234 name: "Some LUNs missing", 235 disks: []DataDisk{ 236 { 237 NameSuffix: "testdisk1", 238 DiskSizeGB: 30, 239 Lun: ptr.To[int32](0), 240 CachingType: "ReadWrite", 241 }, 242 { 243 NameSuffix: "testdisk2", 244 DiskSizeGB: 30, 245 CachingType: "ReadWrite", 246 }, 247 { 248 NameSuffix: "testdisk3", 249 DiskSizeGB: 30, 250 Lun: ptr.To[int32](1), 251 CachingType: "ReadWrite", 252 }, 253 { 254 NameSuffix: "testdisk4", 255 DiskSizeGB: 30, 256 CachingType: "ReadWrite", 257 }, 258 }, 259 output: []DataDisk{ 260 { 261 NameSuffix: "testdisk1", 262 DiskSizeGB: 30, 263 Lun: ptr.To[int32](0), 264 CachingType: "ReadWrite", 265 }, 266 { 267 NameSuffix: "testdisk2", 268 DiskSizeGB: 30, 269 Lun: ptr.To[int32](2), 270 CachingType: "ReadWrite", 271 }, 272 { 273 NameSuffix: "testdisk3", 274 DiskSizeGB: 30, 275 Lun: ptr.To[int32](1), 276 CachingType: "ReadWrite", 277 }, 278 { 279 NameSuffix: "testdisk4", 280 DiskSizeGB: 30, 281 Lun: ptr.To[int32](3), 282 CachingType: "ReadWrite", 283 }, 284 }, 285 }, 286 { 287 name: "CachingType unspecified", 288 disks: []DataDisk{ 289 { 290 NameSuffix: "testdisk1", 291 DiskSizeGB: 30, 292 Lun: ptr.To[int32](0), 293 }, 294 { 295 NameSuffix: "testdisk2", 296 DiskSizeGB: 30, 297 Lun: ptr.To[int32](2), 298 }, 299 { 300 NameSuffix: "testdisk3", 301 DiskSizeGB: 30, 302 ManagedDisk: &ManagedDiskParameters{ 303 StorageAccountType: "UltraSSD_LRS", 304 }, 305 Lun: ptr.To[int32](3), 306 }, 307 }, 308 output: []DataDisk{ 309 { 310 NameSuffix: "testdisk1", 311 DiskSizeGB: 30, 312 Lun: ptr.To[int32](0), 313 CachingType: "ReadWrite", 314 }, 315 { 316 NameSuffix: "testdisk2", 317 DiskSizeGB: 30, 318 Lun: ptr.To[int32](2), 319 CachingType: "ReadWrite", 320 }, 321 { 322 NameSuffix: "testdisk3", 323 DiskSizeGB: 30, 324 Lun: ptr.To[int32](3), 325 ManagedDisk: &ManagedDiskParameters{ 326 StorageAccountType: "UltraSSD_LRS", 327 }, 328 CachingType: "None", 329 }, 330 }, 331 }, 332 } 333 334 for _, c := range cases { 335 tc := c 336 t.Run(tc.name, func(t *testing.T) { 337 t.Parallel() 338 machine := hardcodedAzureMachineWithSSHKey(generateSSHPublicKey(true)) 339 machine.Spec.DataDisks = tc.disks 340 machine.Spec.SetDataDisksDefaults() 341 if !reflect.DeepEqual(machine.Spec.DataDisks, tc.output) { 342 expected, _ := json.MarshalIndent(tc.output, "", "\t") 343 actual, _ := json.MarshalIndent(machine.Spec.DataDisks, "", "\t") 344 t.Errorf("Expected %s, got %s", string(expected), string(actual)) 345 } 346 }) 347 } 348 } 349 350 func TestAzureMachineSpec_SetNetworkInterfacesDefaults(t *testing.T) { 351 tests := []struct { 352 name string 353 machine *AzureMachine 354 want *AzureMachine 355 }{ 356 { 357 name: "defaulting webhook updates machine with deprecated subnetName field", 358 machine: &AzureMachine{ 359 Spec: AzureMachineSpec{ 360 SubnetName: "test-subnet", 361 }, 362 }, 363 want: &AzureMachine{ 364 Spec: AzureMachineSpec{ 365 SubnetName: "", 366 NetworkInterfaces: []NetworkInterface{ 367 { 368 SubnetName: "test-subnet", 369 PrivateIPConfigs: 1, 370 }, 371 }, 372 }, 373 }, 374 }, 375 { 376 name: "defaulting webhook updates machine with deprecated subnetName field and empty NetworkInterfaces slice", 377 machine: &AzureMachine{ 378 Spec: AzureMachineSpec{ 379 SubnetName: "test-subnet", 380 NetworkInterfaces: []NetworkInterface{}, 381 }, 382 }, 383 want: &AzureMachine{ 384 Spec: AzureMachineSpec{ 385 SubnetName: "", 386 NetworkInterfaces: []NetworkInterface{ 387 { 388 SubnetName: "test-subnet", 389 PrivateIPConfigs: 1, 390 }, 391 }, 392 }, 393 }, 394 }, 395 { 396 name: "defaulting webhook updates machine with deprecated acceleratedNetworking field", 397 machine: &AzureMachine{ 398 Spec: AzureMachineSpec{ 399 SubnetName: "test-subnet", 400 AcceleratedNetworking: ptr.To(true), 401 }, 402 }, 403 want: &AzureMachine{ 404 Spec: AzureMachineSpec{ 405 SubnetName: "", 406 AcceleratedNetworking: nil, 407 NetworkInterfaces: []NetworkInterface{ 408 { 409 SubnetName: "test-subnet", 410 PrivateIPConfigs: 1, 411 AcceleratedNetworking: ptr.To(true), 412 }, 413 }, 414 }, 415 }, 416 }, 417 { 418 name: "defaulting webhook does nothing if both new and deprecated subnetName fields are set", 419 machine: &AzureMachine{ 420 Spec: AzureMachineSpec{ 421 SubnetName: "test-subnet", 422 NetworkInterfaces: []NetworkInterface{{ 423 SubnetName: "test-subnet", 424 }}, 425 }, 426 }, 427 want: &AzureMachine{ 428 Spec: AzureMachineSpec{ 429 SubnetName: "test-subnet", 430 AcceleratedNetworking: nil, 431 NetworkInterfaces: []NetworkInterface{ 432 { 433 SubnetName: "test-subnet", 434 }, 435 }, 436 }, 437 }, 438 }, 439 } 440 441 for _, tc := range tests { 442 t.Run(tc.name, func(t *testing.T) { 443 g := NewWithT(t) 444 tc.machine.Spec.SetNetworkInterfacesDefaults() 445 g.Expect(tc.machine).To(Equal(tc.want)) 446 }) 447 } 448 } 449 450 func TestAzureMachineSpec_GetOwnerCluster(t *testing.T) { 451 tests := []struct { 452 name string 453 maxAttempts int 454 wantedName string 455 wantedNamespace string 456 wantErr bool 457 }{ 458 { 459 name: "ownerCluster is returned", 460 maxAttempts: 1, 461 wantedName: "test-cluster", 462 wantedNamespace: "default", 463 wantErr: false, 464 }, 465 { 466 name: "ownerCluster is returned after 2 attempts", 467 maxAttempts: 2, 468 wantedName: "test-cluster", 469 wantedNamespace: "default", 470 wantErr: false, 471 }, 472 { 473 name: "ownerCluster is not returned after 5 attempts", 474 maxAttempts: 5, 475 wantedName: "test-cluster", 476 wantedNamespace: "default", 477 wantErr: true, 478 }, 479 } 480 481 for _, tc := range tests { 482 t.Run(tc.name, func(t *testing.T) { 483 g := NewWithT(t) 484 client := mockClient{ReturnError: tc.wantErr} 485 name, namespace, err := GetOwnerAzureClusterNameAndNamespace(client, "test-cluster", "default", tc.maxAttempts) 486 if tc.wantErr { 487 g.Expect(err).To(HaveOccurred()) 488 } else { 489 g.Expect(err).NotTo(HaveOccurred()) 490 g.Expect(name).To(Equal(tc.wantedName)) 491 g.Expect(namespace).To(Equal(tc.wantedNamespace)) 492 } 493 }) 494 } 495 } 496 497 func TestAzureMachineSpec_GetSubscriptionID(t *testing.T) { 498 tests := []struct { 499 name string 500 maxAttempts int 501 ownerAzureClusterName string 502 ownerAzureClusterNamespace string 503 want string 504 wantErr bool 505 }{ 506 { 507 name: "empty owner cluster name returns error", 508 maxAttempts: 1, 509 ownerAzureClusterName: "", 510 want: "test-subscription-id", 511 wantErr: true, 512 }, 513 { 514 name: "subscription ID is returned", 515 maxAttempts: 1, 516 ownerAzureClusterName: "test-cluster", 517 ownerAzureClusterNamespace: "default", 518 want: "test-subscription-id", 519 wantErr: false, 520 }, 521 { 522 name: "subscription ID is returned after 2 attempts", 523 maxAttempts: 2, 524 ownerAzureClusterName: "test-cluster", 525 ownerAzureClusterNamespace: "default", 526 want: "test-subscription-id", 527 wantErr: false, 528 }, 529 { 530 name: "subscription ID is not returned after 5 attempts", 531 maxAttempts: 5, 532 ownerAzureClusterName: "test-cluster", 533 ownerAzureClusterNamespace: "default", 534 want: "", 535 wantErr: true, 536 }, 537 } 538 539 for _, tc := range tests { 540 t.Run(tc.name, func(t *testing.T) { 541 g := NewWithT(t) 542 client := mockClient{ReturnError: tc.wantErr} 543 result, err := GetSubscriptionID(client, tc.ownerAzureClusterName, tc.ownerAzureClusterNamespace, tc.maxAttempts) 544 if tc.wantErr { 545 g.Expect(err).To(HaveOccurred()) 546 } else { 547 g.Expect(err).NotTo(HaveOccurred()) 548 g.Expect(result).To(Equal(tc.want)) 549 } 550 }) 551 } 552 } 553 554 type mockClient struct { 555 client.Client 556 ReturnError bool 557 } 558 559 func (m mockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { 560 if m.ReturnError { 561 return errors.New("AzureCluster not found: failed to find owner cluster for test-cluster") 562 } 563 // Check if we're calling Get on an AzureCluster or a Cluster 564 switch obj := obj.(type) { 565 case *AzureCluster: 566 obj.Spec.SubscriptionID = "test-subscription-id" 567 case *clusterv1.Cluster: 568 obj.Spec = clusterv1.ClusterSpec{ 569 InfrastructureRef: &corev1.ObjectReference{ 570 Kind: AzureClusterKind, 571 Name: "test-cluster", 572 Namespace: "default", 573 }, 574 ClusterNetwork: &clusterv1.ClusterNetwork{ 575 Services: &clusterv1.NetworkRanges{ 576 CIDRBlocks: []string{"192.168.0.0/26"}, 577 }, 578 }, 579 } 580 default: 581 return errors.New("unexpected object type") 582 } 583 584 return nil 585 } 586 587 func createMachineWithSSHPublicKey(sshPublicKey string) *AzureMachine { 588 machine := hardcodedAzureMachineWithSSHKey(sshPublicKey) 589 return machine 590 } 591 592 func createMachineWithUserAssignedIdentities(identitiesList []UserAssignedIdentity) *AzureMachine { 593 machine := hardcodedAzureMachineWithSSHKey(generateSSHPublicKey(true)) 594 machine.Spec.Identity = VMIdentityUserAssigned 595 machine.Spec.UserAssignedIdentities = identitiesList 596 return machine 597 } 598 599 func hardcodedAzureMachineWithSSHKey(sshPublicKey string) *AzureMachine { 600 return &AzureMachine{ 601 ObjectMeta: metav1.ObjectMeta{ 602 Labels: map[string]string{ 603 clusterv1.ClusterNameLabel: "test-cluster", 604 }, 605 }, 606 Spec: AzureMachineSpec{ 607 SSHPublicKey: sshPublicKey, 608 OSDisk: generateValidOSDisk(), 609 Image: &Image{ 610 SharedGallery: &AzureSharedGalleryImage{ 611 SubscriptionID: "SUB123", 612 ResourceGroup: "RG123", 613 Name: "NAME123", 614 Gallery: "GALLERY1", 615 Version: "1.0.0", 616 }, 617 }, 618 }, 619 } 620 }