sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/scope/machinepool_test.go (about) 1 /* 2 Copyright 2020 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 scope 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "testing" 24 25 "github.com/Azure/azure-sdk-for-go/sdk/azidentity" 26 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" 27 azureautorest "github.com/Azure/go-autorest/autorest/azure" 28 "github.com/Azure/go-autorest/autorest/azure/auth" 29 . "github.com/onsi/gomega" 30 "go.uber.org/mock/gomock" 31 corev1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/util/intstr" 35 "k8s.io/utils/ptr" 36 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 37 "sigs.k8s.io/cluster-api-provider-azure/azure" 38 "sigs.k8s.io/cluster-api-provider-azure/azure/mock_azure" 39 "sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus" 40 "sigs.k8s.io/cluster-api-provider-azure/azure/services/roleassignments" 41 "sigs.k8s.io/cluster-api-provider-azure/azure/services/scalesets" 42 infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" 43 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 44 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 45 "sigs.k8s.io/controller-runtime/pkg/client" 46 "sigs.k8s.io/controller-runtime/pkg/client/fake" 47 ) 48 49 func TestMachinePoolScope_Name(t *testing.T) { 50 tests := []struct { 51 name string 52 machinePoolScope MachinePoolScope 53 want string 54 testLength bool 55 }{ 56 { 57 name: "linux can be any length", 58 machinePoolScope: MachinePoolScope{ 59 MachinePool: nil, 60 AzureMachinePool: &infrav1exp.AzureMachinePool{ 61 ObjectMeta: metav1.ObjectMeta{ 62 Name: "some-really-really-long-name", 63 }, 64 }, 65 ClusterScoper: nil, 66 }, 67 want: "some-really-really-long-name", 68 }, 69 { 70 name: "windows longer than 9 should be shortened", 71 machinePoolScope: MachinePoolScope{ 72 MachinePool: nil, 73 AzureMachinePool: &infrav1exp.AzureMachinePool{ 74 ObjectMeta: metav1.ObjectMeta{ 75 Name: "machine-90123456", 76 }, 77 Spec: infrav1exp.AzureMachinePoolSpec{ 78 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 79 OSDisk: infrav1.OSDisk{ 80 OSType: "Windows", 81 }, 82 }, 83 }, 84 }, 85 ClusterScoper: nil, 86 }, 87 want: "win-23456", 88 }, 89 } 90 91 for _, tt := range tests { 92 t.Run(tt.name, func(t *testing.T) { 93 got := tt.machinePoolScope.Name() 94 if got != tt.want { 95 t.Errorf("MachinePoolScope.Name() = %v, want %v", got, tt.want) 96 } 97 98 if tt.testLength && len(got) > 9 { 99 t.Errorf("Length of MachinePoolScope.Name() = %v, want less than %v", len(got), 9) 100 } 101 }) 102 } 103 } 104 105 func TestMachinePoolScope_ProviderID(t *testing.T) { 106 tests := []struct { 107 name string 108 machinePoolScope MachinePoolScope 109 want string 110 }{ 111 { 112 name: "valid providerID", 113 machinePoolScope: MachinePoolScope{ 114 AzureMachinePool: &infrav1exp.AzureMachinePool{ 115 Spec: infrav1exp.AzureMachinePoolSpec{ 116 ProviderID: "azure:///subscriptions/1234/resourcegroups/my-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cloud-provider-user-identity", 117 }, 118 }, 119 }, 120 want: "cloud-provider-user-identity", 121 }, 122 { 123 name: "valid providerID: VMSS Flex instance", 124 machinePoolScope: MachinePoolScope{ 125 AzureMachinePool: &infrav1exp.AzureMachinePool{ 126 Spec: infrav1exp.AzureMachinePoolSpec{ 127 ProviderID: "azure:///subscriptions/1234/resourceGroups/my-cluster/providers/Microsoft.Compute/virtualMachines/machine-0", 128 }, 129 }, 130 }, 131 want: "machine-0", 132 }, 133 { 134 name: "valid providerID: VMSS Uniform instance", 135 machinePoolScope: MachinePoolScope{ 136 AzureMachinePool: &infrav1exp.AzureMachinePool{ 137 Spec: infrav1exp.AzureMachinePoolSpec{ 138 ProviderID: "azure:///subscriptions/1234/resourceGroups/my-cluster/providers/Microsoft.Compute/virtualMachineScaleSets/my-cluster-mp-0/virtualMachines/0", 139 }, 140 }, 141 }, 142 want: "0", 143 }, 144 { 145 name: "invalid providerID: no cloud provider", 146 machinePoolScope: MachinePoolScope{ 147 AzureMachinePool: &infrav1exp.AzureMachinePool{ 148 Spec: infrav1exp.AzureMachinePoolSpec{ 149 ProviderID: "subscriptions/123/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm", 150 }, 151 }, 152 }, 153 want: "", 154 }, 155 { 156 name: "invalid providerID: incomplete URL", 157 machinePoolScope: MachinePoolScope{ 158 AzureMachinePool: &infrav1exp.AzureMachinePool{ 159 Spec: infrav1exp.AzureMachinePoolSpec{ 160 ProviderID: "azure:///", 161 }, 162 }, 163 }, 164 want: "", 165 }, 166 } 167 168 for _, tt := range tests { 169 t.Run(tt.name, func(t *testing.T) { 170 got := tt.machinePoolScope.ProviderID() 171 if got != tt.want { 172 t.Errorf("MachinePoolScope.ProviderID() = %v, want %v", got, tt.want) 173 } 174 }) 175 } 176 } 177 178 func TestMachinePoolScope_NetworkInterfaces(t *testing.T) { 179 tests := []struct { 180 name string 181 machinePoolScope MachinePoolScope 182 want int 183 }{ 184 { 185 name: "zero network interfaces", 186 machinePoolScope: MachinePoolScope{ 187 MachinePool: nil, 188 AzureMachinePool: &infrav1exp.AzureMachinePool{ 189 ObjectMeta: metav1.ObjectMeta{ 190 Name: "default-nics", 191 }, 192 Spec: infrav1exp.AzureMachinePoolSpec{ 193 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 194 AcceleratedNetworking: ptr.To(true), 195 SubnetName: "node-subnet", 196 }, 197 }, 198 }, 199 ClusterScoper: nil, 200 }, 201 want: 0, 202 }, 203 { 204 name: "one network interface", 205 machinePoolScope: MachinePoolScope{ 206 MachinePool: nil, 207 AzureMachinePool: &infrav1exp.AzureMachinePool{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Name: "single-nic", 210 }, 211 Spec: infrav1exp.AzureMachinePoolSpec{ 212 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 213 NetworkInterfaces: []infrav1.NetworkInterface{ 214 { 215 SubnetName: "node-subnet", 216 }, 217 }, 218 }, 219 }, 220 }, 221 ClusterScoper: nil, 222 }, 223 want: 1, 224 }, 225 { 226 name: "two network interfaces", 227 machinePoolScope: MachinePoolScope{ 228 MachinePool: nil, 229 AzureMachinePool: &infrav1exp.AzureMachinePool{ 230 ObjectMeta: metav1.ObjectMeta{ 231 Name: "dual-nics", 232 }, 233 Spec: infrav1exp.AzureMachinePoolSpec{ 234 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 235 NetworkInterfaces: []infrav1.NetworkInterface{ 236 { 237 SubnetName: "control-plane-subnet", 238 }, 239 { 240 SubnetName: "node-subnet", 241 }, 242 }, 243 }, 244 }, 245 }, 246 ClusterScoper: nil, 247 }, 248 want: 2, 249 }, 250 } 251 252 for _, tt := range tests { 253 t.Run(tt.name, func(t *testing.T) { 254 got := len(tt.machinePoolScope.AzureMachinePool.Spec.Template.NetworkInterfaces) 255 if got != tt.want { 256 t.Errorf("MachinePoolScope.Name() = %v, want %v", got, tt.want) 257 } 258 }) 259 } 260 } 261 262 func TestMachinePoolScope_MaxSurge(t *testing.T) { 263 cases := []struct { 264 Name string 265 Setup func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) 266 Verify func(g *WithT, surge int, err error) 267 }{ 268 { 269 Name: "default surge should be 1 if no deployment strategy is set", 270 Verify: func(g *WithT, surge int, err error) { 271 g.Expect(surge).To(Equal(1)) 272 g.Expect(err).NotTo(HaveOccurred()) 273 }, 274 }, 275 { 276 Name: "default surge should be 1 regardless of replica count with no surger", 277 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) { 278 mp.Spec.Replicas = ptr.To[int32](3) 279 }, 280 Verify: func(g *WithT, surge int, err error) { 281 g.Expect(surge).To(Equal(1)) 282 g.Expect(err).NotTo(HaveOccurred()) 283 }, 284 }, 285 { 286 Name: "default surge should be 2 as specified by the surger", 287 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) { 288 mp.Spec.Replicas = ptr.To[int32](3) 289 two := intstr.FromInt(2) 290 amp.Spec.Strategy = infrav1exp.AzureMachinePoolDeploymentStrategy{ 291 Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, 292 RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ 293 MaxSurge: &two, 294 }, 295 } 296 }, 297 Verify: func(g *WithT, surge int, err error) { 298 g.Expect(surge).To(Equal(2)) 299 g.Expect(err).NotTo(HaveOccurred()) 300 }, 301 }, 302 { 303 Name: "default surge should be 2 (50%) of the desired replicas", 304 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) { 305 mp.Spec.Replicas = ptr.To[int32](4) 306 fiftyPercent := intstr.FromString("50%") 307 amp.Spec.Strategy = infrav1exp.AzureMachinePoolDeploymentStrategy{ 308 Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, 309 RollingUpdate: &infrav1exp.MachineRollingUpdateDeployment{ 310 MaxSurge: &fiftyPercent, 311 }, 312 } 313 }, 314 Verify: func(g *WithT, surge int, err error) { 315 g.Expect(surge).To(Equal(2)) 316 g.Expect(err).NotTo(HaveOccurred()) 317 }, 318 }, 319 } 320 321 for _, c := range cases { 322 t.Run(c.Name, func(t *testing.T) { 323 var ( 324 g = NewWithT(t) 325 mockCtrl = gomock.NewController(t) 326 amp = &infrav1exp.AzureMachinePool{ 327 ObjectMeta: metav1.ObjectMeta{ 328 Name: "amp1", 329 Namespace: "default", 330 OwnerReferences: []metav1.OwnerReference{ 331 { 332 Name: "mp1", 333 Kind: "MachinePool", 334 APIVersion: expv1.GroupVersion.String(), 335 }, 336 }, 337 }, 338 } 339 mp = &expv1.MachinePool{ 340 ObjectMeta: metav1.ObjectMeta{ 341 Name: "mp1", 342 Namespace: "default", 343 }, 344 } 345 ) 346 defer mockCtrl.Finish() 347 348 if c.Setup != nil { 349 c.Setup(mp, amp) 350 } 351 352 s := &MachinePoolScope{ 353 MachinePool: mp, 354 AzureMachinePool: amp, 355 } 356 surge, err := s.MaxSurge() 357 c.Verify(g, surge, err) 358 }) 359 } 360 } 361 362 func TestMachinePoolScope_SaveVMImageToStatus(t *testing.T) { 363 var ( 364 g = NewWithT(t) 365 mockCtrl = gomock.NewController(t) 366 amp = &infrav1exp.AzureMachinePool{ 367 ObjectMeta: metav1.ObjectMeta{ 368 Name: "amp1", 369 Namespace: "default", 370 OwnerReferences: []metav1.OwnerReference{ 371 { 372 Name: "mp1", 373 Kind: "MachinePool", 374 APIVersion: expv1.GroupVersion.String(), 375 }, 376 }, 377 }, 378 } 379 s = &MachinePoolScope{ 380 AzureMachinePool: amp, 381 } 382 image = &infrav1.Image{ 383 Marketplace: &infrav1.AzureMarketplaceImage{ 384 ImagePlan: infrav1.ImagePlan{ 385 Publisher: "cncf-upstream", 386 Offer: "capi", 387 SKU: "k8s-1dot19dot11-ubuntu-1804", 388 }, 389 Version: "latest", 390 ThirdPartyImage: false, 391 }, 392 } 393 ) 394 defer mockCtrl.Finish() 395 396 s.SaveVMImageToStatus(image) 397 g.Expect(s.AzureMachinePool.Status.Image).To(Equal(image)) 398 } 399 400 func TestMachinePoolScope_GetVMImage(t *testing.T) { 401 mockCtrl := gomock.NewController(t) 402 defer mockCtrl.Finish() 403 404 clusterMock := mock_azure.NewMockClusterScoper(mockCtrl) 405 clusterMock.EXPECT().Location().AnyTimes() 406 clusterMock.EXPECT().SubscriptionID().AnyTimes() 407 clusterMock.EXPECT().CloudEnvironment().AnyTimes() 408 clusterMock.EXPECT().Token().Return(&azidentity.DefaultAzureCredential{}).AnyTimes() 409 cases := []struct { 410 Name string 411 Setup func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) 412 Verify func(g *WithT, amp *infrav1exp.AzureMachinePool, vmImage *infrav1.Image, err error) 413 }{ 414 { 415 Name: "should set and default the image if no image is specified for the AzureMachinePool", 416 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) { 417 mp.Spec.Template.Spec.Version = ptr.To("v1.19.11") 418 }, 419 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, vmImage *infrav1.Image, err error) { 420 g.Expect(err).NotTo(HaveOccurred()) 421 image := &infrav1.Image{ 422 Marketplace: &infrav1.AzureMarketplaceImage{ 423 ImagePlan: infrav1.ImagePlan{ 424 Publisher: "cncf-upstream", 425 Offer: "capi", 426 SKU: "k8s-1dot19dot11-ubuntu-1804", 427 }, 428 Version: "latest", 429 ThirdPartyImage: false, 430 }, 431 } 432 g.Expect(vmImage).To(Equal(image)) 433 g.Expect(amp.Spec.Template.Image).To(BeNil()) 434 }, 435 }, 436 { 437 Name: "should not default or set the image on the AzureMachinePool if it already exists", 438 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool) { 439 mp.Spec.Template.Spec.Version = ptr.To("v1.19.11") 440 amp.Spec.Template.Image = &infrav1.Image{ 441 Marketplace: &infrav1.AzureMarketplaceImage{ 442 ImagePlan: infrav1.ImagePlan{ 443 Publisher: "cncf-upstream", 444 Offer: "capi", 445 SKU: "k8s-1dot19dot19-ubuntu-1804", 446 }, 447 Version: "latest", 448 ThirdPartyImage: false, 449 }, 450 } 451 }, 452 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, vmImage *infrav1.Image, err error) { 453 g.Expect(err).NotTo(HaveOccurred()) 454 image := &infrav1.Image{ 455 Marketplace: &infrav1.AzureMarketplaceImage{ 456 ImagePlan: infrav1.ImagePlan{ 457 Publisher: "cncf-upstream", 458 Offer: "capi", 459 SKU: "k8s-1dot19dot19-ubuntu-1804", 460 }, 461 Version: "latest", 462 ThirdPartyImage: false, 463 }, 464 } 465 g.Expect(vmImage).To(Equal(image)) 466 g.Expect(amp.Spec.Template.Image).To(Equal(image)) 467 }, 468 }, 469 } 470 471 for _, c := range cases { 472 t.Run(c.Name, func(t *testing.T) { 473 var ( 474 g = NewWithT(t) 475 mockCtrl = gomock.NewController(t) 476 amp = &infrav1exp.AzureMachinePool{ 477 ObjectMeta: metav1.ObjectMeta{ 478 Name: "amp1", 479 Namespace: "default", 480 OwnerReferences: []metav1.OwnerReference{ 481 { 482 Name: "mp1", 483 Kind: "MachinePool", 484 APIVersion: expv1.GroupVersion.String(), 485 }, 486 }, 487 }, 488 } 489 mp = &expv1.MachinePool{ 490 ObjectMeta: metav1.ObjectMeta{ 491 Name: "mp1", 492 Namespace: "default", 493 }, 494 } 495 ) 496 defer mockCtrl.Finish() 497 498 if c.Setup != nil { 499 c.Setup(mp, amp) 500 } 501 502 s := &MachinePoolScope{ 503 MachinePool: mp, 504 AzureMachinePool: amp, 505 ClusterScoper: clusterMock, 506 } 507 image, err := s.GetVMImage(context.TODO()) 508 c.Verify(g, amp, image, err) 509 }) 510 } 511 } 512 513 func TestMachinePoolScope_NeedsRequeue(t *testing.T) { 514 cases := []struct { 515 Name string 516 Setup func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmss *azure.VMSS) 517 Verify func(g *WithT, requeue bool) 518 }{ 519 { 520 Name: "should requeue if the machine is not in succeeded state", 521 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmss *azure.VMSS) { 522 creating := infrav1.Creating 523 mp.Spec.Replicas = ptr.To[int32](0) 524 amp.Status.ProvisioningState = &creating 525 }, 526 Verify: func(g *WithT, requeue bool) { 527 g.Expect(requeue).To(BeTrue()) 528 }, 529 }, 530 { 531 Name: "should not requeue if the machine is in succeeded state", 532 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmss *azure.VMSS) { 533 succeeded := infrav1.Succeeded 534 mp.Spec.Replicas = ptr.To[int32](0) 535 amp.Status.ProvisioningState = &succeeded 536 }, 537 Verify: func(g *WithT, requeue bool) { 538 g.Expect(requeue).To(BeFalse()) 539 }, 540 }, 541 { 542 Name: "should requeue if the machine is in succeeded state but desired replica count does not match", 543 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmss *azure.VMSS) { 544 succeeded := infrav1.Succeeded 545 mp.Spec.Replicas = ptr.To[int32](1) 546 amp.Status.ProvisioningState = &succeeded 547 }, 548 Verify: func(g *WithT, requeue bool) { 549 g.Expect(requeue).To(BeTrue()) 550 }, 551 }, 552 { 553 Name: "should not requeue if the machine is in succeeded state but desired replica count does match", 554 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmss *azure.VMSS) { 555 succeeded := infrav1.Succeeded 556 mp.Spec.Replicas = ptr.To[int32](1) 557 amp.Status.ProvisioningState = &succeeded 558 vmss.Instances = []azure.VMSSVM{ 559 { 560 Name: "instance1", 561 }, 562 } 563 }, 564 Verify: func(g *WithT, requeue bool) { 565 g.Expect(requeue).To(BeFalse()) 566 }, 567 }, 568 { 569 Name: "should requeue if an instance VM image does not match the VM image of the VMSS", 570 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmss *azure.VMSS) { 571 succeeded := infrav1.Succeeded 572 mp.Spec.Replicas = ptr.To[int32](1) 573 amp.Status.ProvisioningState = &succeeded 574 vmss.Instances = []azure.VMSSVM{ 575 { 576 Name: "instance1", 577 Image: infrav1.Image{ 578 Marketplace: &infrav1.AzureMarketplaceImage{ 579 Version: "foo1", 580 }, 581 }, 582 }, 583 } 584 }, 585 Verify: func(g *WithT, requeue bool) { 586 g.Expect(requeue).To(BeTrue()) 587 }, 588 }, 589 } 590 591 for _, c := range cases { 592 t.Run(c.Name, func(t *testing.T) { 593 var ( 594 g = NewWithT(t) 595 mockCtrl = gomock.NewController(t) 596 amp = &infrav1exp.AzureMachinePool{ 597 ObjectMeta: metav1.ObjectMeta{ 598 Name: "amp1", 599 Namespace: "default", 600 OwnerReferences: []metav1.OwnerReference{ 601 { 602 Name: "mp1", 603 Kind: "MachinePool", 604 APIVersion: expv1.GroupVersion.String(), 605 }, 606 }, 607 }, 608 } 609 mp = &expv1.MachinePool{ 610 ObjectMeta: metav1.ObjectMeta{ 611 Name: "mp1", 612 Namespace: "default", 613 }, 614 } 615 vmssState = &azure.VMSS{} 616 ) 617 defer mockCtrl.Finish() 618 619 if c.Setup != nil { 620 c.Setup(mp, amp, vmssState) 621 } 622 623 s := &MachinePoolScope{ 624 vmssState: vmssState, 625 MachinePool: mp, 626 AzureMachinePool: amp, 627 } 628 c.Verify(g, s.NeedsRequeue()) 629 }) 630 } 631 } 632 633 func TestMachinePoolScope_updateReplicasAndProviderIDs(t *testing.T) { 634 scheme := runtime.NewScheme() 635 _ = clusterv1.AddToScheme(scheme) 636 _ = infrav1exp.AddToScheme(scheme) 637 _ = expv1.AddToScheme(scheme) 638 639 cases := []struct { 640 Name string 641 Setup func(cb *fake.ClientBuilder) 642 Verify func(g *WithT, amp *infrav1exp.AzureMachinePool, err error) 643 }{ 644 { 645 Name: "if there are three ready machines with matching labels, then should count them", 646 Setup: func(cb *fake.ClientBuilder) { 647 for _, machine := range getReadyAzureMachinePoolMachines(3) { 648 obj := machine 649 cb.WithObjects(&obj) 650 } 651 }, 652 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, err error) { 653 g.Expect(err).NotTo(HaveOccurred()) 654 g.Expect(amp.Status.Replicas).To(BeEquivalentTo(3)) 655 g.Expect(amp.Spec.ProviderIDList).To(ConsistOf("azure://foo/ampm0", "azure://foo/ampm1", "azure://foo/ampm2")) 656 }, 657 }, 658 { 659 Name: "should only count machines with matching machine pool label", 660 Setup: func(cb *fake.ClientBuilder) { 661 machines := getReadyAzureMachinePoolMachines(3) 662 machines[0].Labels[infrav1exp.MachinePoolNameLabel] = "not_correct" 663 for _, machine := range machines { 664 obj := machine 665 cb.WithObjects(&obj) 666 } 667 }, 668 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, err error) { 669 g.Expect(err).NotTo(HaveOccurred()) 670 g.Expect(amp.Status.Replicas).To(BeEquivalentTo(2)) 671 }, 672 }, 673 { 674 Name: "should only count machines with matching cluster name label", 675 Setup: func(cb *fake.ClientBuilder) { 676 machines := getReadyAzureMachinePoolMachines(3) 677 machines[0].Labels[clusterv1.ClusterNameLabel] = "not_correct" 678 for _, machine := range machines { 679 obj := machine 680 cb.WithObjects(&obj) 681 } 682 }, 683 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, err error) { 684 g.Expect(err).NotTo(HaveOccurred()) 685 g.Expect(amp.Status.Replicas).To(BeEquivalentTo(2)) 686 }, 687 }, 688 } 689 690 for _, c := range cases { 691 t.Run(c.Name, func(t *testing.T) { 692 var ( 693 g = NewWithT(t) 694 mockCtrl = gomock.NewController(t) 695 cb = fake.NewClientBuilder().WithScheme(scheme) 696 cluster = &clusterv1.Cluster{ 697 ObjectMeta: metav1.ObjectMeta{ 698 Name: "cluster1", 699 Namespace: "default", 700 }, 701 Spec: clusterv1.ClusterSpec{ 702 InfrastructureRef: &corev1.ObjectReference{ 703 Name: "azCluster1", 704 }, 705 }, 706 Status: clusterv1.ClusterStatus{ 707 InfrastructureReady: true, 708 }, 709 } 710 mp = &expv1.MachinePool{ 711 ObjectMeta: metav1.ObjectMeta{ 712 Name: "mp1", 713 Namespace: "default", 714 }, 715 } 716 amp = &infrav1exp.AzureMachinePool{ 717 ObjectMeta: metav1.ObjectMeta{ 718 Name: "amp1", 719 Namespace: "default", 720 OwnerReferences: []metav1.OwnerReference{ 721 { 722 Name: "mp1", 723 Kind: "MachinePool", 724 APIVersion: expv1.GroupVersion.String(), 725 }, 726 }, 727 }, 728 } 729 ) 730 defer mockCtrl.Finish() 731 732 c.Setup(cb.WithObjects(amp, cluster)) 733 s := &MachinePoolScope{ 734 client: cb.Build(), 735 ClusterScoper: &ClusterScope{ 736 Cluster: cluster, 737 }, 738 AzureMachinePool: amp, 739 MachinePool: mp, 740 } 741 err := s.updateReplicasAndProviderIDs(context.TODO()) 742 c.Verify(g, s.AzureMachinePool, err) 743 }) 744 } 745 } 746 747 func TestMachinePoolScope_RoleAssignmentSpecs(t *testing.T) { 748 tests := []struct { 749 name string 750 machinePoolScope MachinePoolScope 751 want []azure.ResourceSpecGetter 752 }{ 753 { 754 name: "returns empty if VM identity is not system assigned", 755 machinePoolScope: MachinePoolScope{ 756 AzureMachinePool: &infrav1exp.AzureMachinePool{ 757 ObjectMeta: metav1.ObjectMeta{ 758 Name: "machine-name", 759 }, 760 }, 761 }, 762 want: []azure.ResourceSpecGetter{}, 763 }, 764 { 765 name: "returns role assignment spec if VM identity is system assigned", 766 machinePoolScope: MachinePoolScope{ 767 MachinePool: &expv1.MachinePool{}, 768 AzureMachinePool: &infrav1exp.AzureMachinePool{ 769 ObjectMeta: metav1.ObjectMeta{ 770 Name: "machine-name", 771 }, 772 Spec: infrav1exp.AzureMachinePoolSpec{ 773 Identity: infrav1.VMIdentitySystemAssigned, 774 SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ 775 Name: "role-assignment-name", 776 }, 777 }, 778 }, 779 ClusterScoper: &ClusterScope{ 780 AzureClients: AzureClients{ 781 EnvironmentSettings: auth.EnvironmentSettings{ 782 Values: map[string]string{ 783 auth.SubscriptionID: "123", 784 }, 785 }, 786 }, 787 AzureCluster: &infrav1.AzureCluster{ 788 Spec: infrav1.AzureClusterSpec{ 789 ResourceGroup: "my-rg", 790 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 791 Location: "westus", 792 }, 793 }, 794 }, 795 }, 796 }, 797 want: []azure.ResourceSpecGetter{ 798 &roleassignments.RoleAssignmentSpec{ 799 ResourceType: azure.VirtualMachineScaleSet, 800 MachineName: "machine-name", 801 Name: "role-assignment-name", 802 ResourceGroup: "my-rg", 803 PrincipalID: ptr.To("fakePrincipalID"), 804 PrincipalType: armauthorization.PrincipalTypeServicePrincipal, 805 }, 806 }, 807 }, 808 { 809 name: "returns role assignment spec if scope and role definition ID are set", 810 machinePoolScope: MachinePoolScope{ 811 MachinePool: &expv1.MachinePool{}, 812 AzureMachinePool: &infrav1exp.AzureMachinePool{ 813 ObjectMeta: metav1.ObjectMeta{ 814 Name: "machine-name", 815 }, 816 Spec: infrav1exp.AzureMachinePoolSpec{ 817 Identity: infrav1.VMIdentitySystemAssigned, 818 SystemAssignedIdentityRole: &infrav1.SystemAssignedIdentityRole{ 819 Name: "role-assignment-name", 820 Scope: "scope", 821 DefinitionID: "role-definition-id", 822 }, 823 }, 824 }, 825 ClusterScoper: &ClusterScope{ 826 AzureClients: AzureClients{ 827 EnvironmentSettings: auth.EnvironmentSettings{ 828 Values: map[string]string{ 829 auth.SubscriptionID: "123", 830 }, 831 }, 832 }, 833 AzureCluster: &infrav1.AzureCluster{ 834 Spec: infrav1.AzureClusterSpec{ 835 ResourceGroup: "my-rg", 836 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 837 Location: "westus", 838 }, 839 }, 840 }, 841 }, 842 }, 843 want: []azure.ResourceSpecGetter{ 844 &roleassignments.RoleAssignmentSpec{ 845 ResourceType: azure.VirtualMachineScaleSet, 846 MachineName: "machine-name", 847 Name: "role-assignment-name", 848 ResourceGroup: "my-rg", 849 Scope: "scope", 850 RoleDefinitionID: "role-definition-id", 851 PrincipalID: ptr.To("fakePrincipalID"), 852 PrincipalType: armauthorization.PrincipalTypeServicePrincipal, 853 }, 854 }, 855 }, 856 } 857 for _, tt := range tests { 858 t.Run(tt.name, func(t *testing.T) { 859 if got := tt.machinePoolScope.RoleAssignmentSpecs(ptr.To("fakePrincipalID")); !reflect.DeepEqual(got, tt.want) { 860 t.Errorf("RoleAssignmentSpecs() = %v, want %v", got, tt.want) 861 } 862 }) 863 } 864 } 865 866 func TestMachinePoolScope_VMSSExtensionSpecs(t *testing.T) { 867 tests := []struct { 868 name string 869 machinePoolScope MachinePoolScope 870 want []azure.ResourceSpecGetter 871 }{ 872 { 873 name: "If OS type is Linux and cloud is AzurePublicCloud, it returns ExtensionSpec", 874 machinePoolScope: MachinePoolScope{ 875 MachinePool: &expv1.MachinePool{}, 876 AzureMachinePool: &infrav1exp.AzureMachinePool{ 877 ObjectMeta: metav1.ObjectMeta{ 878 Name: "machinepool-name", 879 }, 880 Spec: infrav1exp.AzureMachinePoolSpec{ 881 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 882 OSDisk: infrav1.OSDisk{ 883 OSType: "Linux", 884 }, 885 }, 886 }, 887 }, 888 ClusterScoper: &ClusterScope{ 889 AzureClients: AzureClients{ 890 EnvironmentSettings: auth.EnvironmentSettings{ 891 Environment: azureautorest.Environment{ 892 Name: azureautorest.PublicCloud.Name, 893 }, 894 }, 895 }, 896 AzureCluster: &infrav1.AzureCluster{ 897 Spec: infrav1.AzureClusterSpec{ 898 ResourceGroup: "my-rg", 899 }, 900 }, 901 }, 902 cache: &MachinePoolCache{ 903 VMSKU: resourceskus.SKU{}, 904 }, 905 }, 906 want: []azure.ResourceSpecGetter{ 907 &scalesets.VMSSExtensionSpec{ 908 ExtensionSpec: azure.ExtensionSpec{ 909 Name: "CAPZ.Linux.Bootstrapping", 910 VMName: "machinepool-name", 911 Publisher: "Microsoft.Azure.ContainerUpstream", 912 Version: "1.0", 913 ProtectedSettings: map[string]string{ 914 "commandToExecute": azure.LinuxBootstrapExtensionCommand, 915 }, 916 }, 917 ResourceGroup: "my-rg", 918 }, 919 }, 920 }, 921 { 922 name: "If OS type is Linux and cloud is not AzurePublicCloud, it returns empty", 923 machinePoolScope: MachinePoolScope{ 924 MachinePool: &expv1.MachinePool{}, 925 AzureMachinePool: &infrav1exp.AzureMachinePool{ 926 ObjectMeta: metav1.ObjectMeta{ 927 Name: "machinepool-name", 928 }, 929 Spec: infrav1exp.AzureMachinePoolSpec{ 930 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 931 OSDisk: infrav1.OSDisk{ 932 OSType: "Linux", 933 }, 934 }, 935 }, 936 }, 937 ClusterScoper: &ClusterScope{ 938 AzureClients: AzureClients{ 939 EnvironmentSettings: auth.EnvironmentSettings{ 940 Environment: azureautorest.Environment{ 941 Name: azureautorest.USGovernmentCloud.Name, 942 }, 943 }, 944 }, 945 AzureCluster: &infrav1.AzureCluster{ 946 Spec: infrav1.AzureClusterSpec{ 947 ResourceGroup: "my-rg", 948 }, 949 }, 950 }, 951 cache: &MachinePoolCache{ 952 VMSKU: resourceskus.SKU{}, 953 }, 954 }, 955 want: []azure.ResourceSpecGetter{}, 956 }, 957 { 958 name: "If OS type is Windows and cloud is AzurePublicCloud, it returns ExtensionSpec", 959 machinePoolScope: MachinePoolScope{ 960 MachinePool: &expv1.MachinePool{}, 961 AzureMachinePool: &infrav1exp.AzureMachinePool{ 962 ObjectMeta: metav1.ObjectMeta{ 963 // Note: machine pool names longer than 9 characters get truncated. See MachinePoolScope::Name() for more details. 964 Name: "winpool", 965 }, 966 Spec: infrav1exp.AzureMachinePoolSpec{ 967 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 968 OSDisk: infrav1.OSDisk{ 969 OSType: "Windows", 970 }, 971 }, 972 }, 973 }, 974 ClusterScoper: &ClusterScope{ 975 AzureClients: AzureClients{ 976 EnvironmentSettings: auth.EnvironmentSettings{ 977 Environment: azureautorest.Environment{ 978 Name: azureautorest.PublicCloud.Name, 979 }, 980 }, 981 }, 982 AzureCluster: &infrav1.AzureCluster{ 983 Spec: infrav1.AzureClusterSpec{ 984 ResourceGroup: "my-rg", 985 }, 986 }, 987 }, 988 cache: &MachinePoolCache{ 989 VMSKU: resourceskus.SKU{}, 990 }, 991 }, 992 want: []azure.ResourceSpecGetter{ 993 &scalesets.VMSSExtensionSpec{ 994 ExtensionSpec: azure.ExtensionSpec{ 995 Name: "CAPZ.Windows.Bootstrapping", 996 // Note: machine pool names longer than 9 characters get truncated. See MachinePoolScope::Name() for more details. 997 VMName: "winpool", 998 Publisher: "Microsoft.Azure.ContainerUpstream", 999 Version: "1.0", 1000 ProtectedSettings: map[string]string{ 1001 "commandToExecute": azure.WindowsBootstrapExtensionCommand, 1002 }, 1003 }, 1004 ResourceGroup: "my-rg", 1005 }, 1006 }, 1007 }, 1008 { 1009 name: "If OS type is Windows and cloud is not AzurePublicCloud, it returns empty", 1010 machinePoolScope: MachinePoolScope{ 1011 MachinePool: &expv1.MachinePool{}, 1012 AzureMachinePool: &infrav1exp.AzureMachinePool{ 1013 ObjectMeta: metav1.ObjectMeta{ 1014 Name: "machinepool-name", 1015 }, 1016 Spec: infrav1exp.AzureMachinePoolSpec{ 1017 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 1018 OSDisk: infrav1.OSDisk{ 1019 OSType: "Windows", 1020 }, 1021 }, 1022 }, 1023 }, 1024 ClusterScoper: &ClusterScope{ 1025 AzureClients: AzureClients{ 1026 EnvironmentSettings: auth.EnvironmentSettings{ 1027 Environment: azureautorest.Environment{ 1028 Name: azureautorest.USGovernmentCloud.Name, 1029 }, 1030 }, 1031 }, 1032 AzureCluster: &infrav1.AzureCluster{ 1033 Spec: infrav1.AzureClusterSpec{ 1034 ResourceGroup: "my-rg", 1035 }, 1036 }, 1037 }, 1038 cache: &MachinePoolCache{ 1039 VMSKU: resourceskus.SKU{}, 1040 }, 1041 }, 1042 want: []azure.ResourceSpecGetter{}, 1043 }, 1044 { 1045 name: "If OS type is not Linux or Windows and cloud is AzurePublicCloud, it returns empty", 1046 machinePoolScope: MachinePoolScope{ 1047 MachinePool: &expv1.MachinePool{}, 1048 AzureMachinePool: &infrav1exp.AzureMachinePool{ 1049 ObjectMeta: metav1.ObjectMeta{ 1050 Name: "machinepool-name", 1051 }, 1052 Spec: infrav1exp.AzureMachinePoolSpec{ 1053 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 1054 OSDisk: infrav1.OSDisk{ 1055 OSType: "Other", 1056 }, 1057 }, 1058 }, 1059 }, 1060 ClusterScoper: &ClusterScope{ 1061 AzureClients: AzureClients{ 1062 EnvironmentSettings: auth.EnvironmentSettings{ 1063 Environment: azureautorest.Environment{ 1064 Name: azureautorest.PublicCloud.Name, 1065 }, 1066 }, 1067 }, 1068 AzureCluster: &infrav1.AzureCluster{ 1069 Spec: infrav1.AzureClusterSpec{ 1070 ResourceGroup: "my-rg", 1071 }, 1072 }, 1073 }, 1074 cache: &MachinePoolCache{ 1075 VMSKU: resourceskus.SKU{}, 1076 }, 1077 }, 1078 want: []azure.ResourceSpecGetter{}, 1079 }, 1080 { 1081 name: "If OS type is not Windows or Linux and cloud is not AzurePublicCloud, it returns empty", 1082 machinePoolScope: MachinePoolScope{ 1083 MachinePool: &expv1.MachinePool{}, 1084 AzureMachinePool: &infrav1exp.AzureMachinePool{ 1085 ObjectMeta: metav1.ObjectMeta{ 1086 Name: "machinepool-name", 1087 }, 1088 Spec: infrav1exp.AzureMachinePoolSpec{ 1089 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 1090 OSDisk: infrav1.OSDisk{ 1091 OSType: "Other", 1092 }, 1093 }, 1094 }, 1095 }, 1096 ClusterScoper: &ClusterScope{ 1097 AzureClients: AzureClients{ 1098 EnvironmentSettings: auth.EnvironmentSettings{ 1099 Environment: azureautorest.Environment{ 1100 Name: azureautorest.USGovernmentCloud.Name, 1101 }, 1102 }, 1103 }, 1104 AzureCluster: &infrav1.AzureCluster{ 1105 Spec: infrav1.AzureClusterSpec{ 1106 ResourceGroup: "my-rg", 1107 }, 1108 }, 1109 }, 1110 cache: &MachinePoolCache{ 1111 VMSKU: resourceskus.SKU{}, 1112 }, 1113 }, 1114 want: []azure.ResourceSpecGetter{}, 1115 }, 1116 { 1117 name: "If a custom VM extension is specified, it returns the custom VM extension", 1118 machinePoolScope: MachinePoolScope{ 1119 MachinePool: &expv1.MachinePool{}, 1120 AzureMachinePool: &infrav1exp.AzureMachinePool{ 1121 ObjectMeta: metav1.ObjectMeta{ 1122 Name: "machinepool-name", 1123 }, 1124 Spec: infrav1exp.AzureMachinePoolSpec{ 1125 Template: infrav1exp.AzureMachinePoolMachineTemplate{ 1126 OSDisk: infrav1.OSDisk{ 1127 OSType: "Linux", 1128 }, 1129 VMExtensions: []infrav1.VMExtension{ 1130 { 1131 Name: "custom-vm-extension", 1132 Publisher: "Microsoft.Azure.Extensions", 1133 Version: "2.0", 1134 Settings: map[string]string{ 1135 "timestamp": "1234567890", 1136 }, 1137 ProtectedSettings: map[string]string{ 1138 "commandToExecute": "echo hello world", 1139 }, 1140 }, 1141 }, 1142 }, 1143 }, 1144 }, 1145 ClusterScoper: &ClusterScope{ 1146 AzureClients: AzureClients{ 1147 EnvironmentSettings: auth.EnvironmentSettings{ 1148 Environment: azureautorest.Environment{ 1149 Name: azureautorest.PublicCloud.Name, 1150 }, 1151 }, 1152 }, 1153 AzureCluster: &infrav1.AzureCluster{ 1154 Spec: infrav1.AzureClusterSpec{ 1155 ResourceGroup: "my-rg", 1156 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 1157 Location: "westus", 1158 }, 1159 }, 1160 }, 1161 }, 1162 cache: &MachinePoolCache{ 1163 VMSKU: resourceskus.SKU{}, 1164 }, 1165 }, 1166 want: []azure.ResourceSpecGetter{ 1167 &scalesets.VMSSExtensionSpec{ 1168 ExtensionSpec: azure.ExtensionSpec{ 1169 Name: "custom-vm-extension", 1170 VMName: "machinepool-name", 1171 Publisher: "Microsoft.Azure.Extensions", 1172 Version: "2.0", 1173 Settings: map[string]string{ 1174 "timestamp": "1234567890", 1175 }, 1176 ProtectedSettings: map[string]string{ 1177 "commandToExecute": "echo hello world", 1178 }, 1179 }, 1180 ResourceGroup: "my-rg", 1181 }, 1182 &scalesets.VMSSExtensionSpec{ 1183 ExtensionSpec: azure.ExtensionSpec{ 1184 Name: "CAPZ.Linux.Bootstrapping", 1185 VMName: "machinepool-name", 1186 Publisher: "Microsoft.Azure.ContainerUpstream", 1187 Version: "1.0", 1188 ProtectedSettings: map[string]string{ 1189 "commandToExecute": azure.LinuxBootstrapExtensionCommand, 1190 }, 1191 }, 1192 ResourceGroup: "my-rg", 1193 }, 1194 }, 1195 }, 1196 } 1197 for _, tt := range tests { 1198 t.Run(tt.name, func(t *testing.T) { 1199 if got := tt.machinePoolScope.VMSSExtensionSpecs(); !reflect.DeepEqual(got, tt.want) { 1200 t.Errorf("VMSSExtensionSpecs() = \n%s, want \n%s", specArrayToString(got), specArrayToString(tt.want)) 1201 } 1202 }) 1203 } 1204 } 1205 1206 func getReadyAzureMachinePoolMachines(count int32) []infrav1exp.AzureMachinePoolMachine { 1207 machines := make([]infrav1exp.AzureMachinePoolMachine, count) 1208 succeeded := infrav1.Succeeded 1209 for i := 0; i < int(count); i++ { 1210 machines[i] = infrav1exp.AzureMachinePoolMachine{ 1211 ObjectMeta: metav1.ObjectMeta{ 1212 Name: fmt.Sprintf("ampm%d", i), 1213 Namespace: "default", 1214 OwnerReferences: []metav1.OwnerReference{ 1215 { 1216 Name: "amp", 1217 Kind: infrav1.AzureMachinePoolKind, 1218 APIVersion: infrav1exp.GroupVersion.String(), 1219 }, 1220 }, 1221 Labels: map[string]string{ 1222 clusterv1.ClusterNameLabel: "cluster1", 1223 infrav1exp.MachinePoolNameLabel: "amp1", 1224 clusterv1.MachinePoolNameLabel: "mp1", 1225 "cluster1": string(infrav1.ResourceLifecycleOwned), 1226 }, 1227 }, 1228 Spec: infrav1exp.AzureMachinePoolMachineSpec{ 1229 ProviderID: fmt.Sprintf("azure://foo/ampm%d", i), 1230 }, 1231 Status: infrav1exp.AzureMachinePoolMachineStatus{ 1232 Ready: true, 1233 ProvisioningState: &succeeded, 1234 }, 1235 } 1236 } 1237 1238 return machines 1239 } 1240 1241 func getAzureMachinePoolMachine(index int) infrav1exp.AzureMachinePoolMachine { 1242 return infrav1exp.AzureMachinePoolMachine{ 1243 ObjectMeta: metav1.ObjectMeta{ 1244 Name: fmt.Sprintf("ampm%d", index), 1245 Namespace: "default", 1246 OwnerReferences: []metav1.OwnerReference{ 1247 { 1248 Name: "amp", 1249 Kind: "AzureMachinePool", 1250 APIVersion: infrav1exp.GroupVersion.String(), 1251 }, 1252 }, 1253 Labels: map[string]string{ 1254 clusterv1.ClusterNameLabel: "cluster1", 1255 infrav1exp.MachinePoolNameLabel: "amp1", 1256 clusterv1.MachinePoolNameLabel: "mp1", 1257 "cluster1": string(infrav1.ResourceLifecycleOwned), 1258 }, 1259 }, 1260 Spec: infrav1exp.AzureMachinePoolMachineSpec{ 1261 ProviderID: fmt.Sprintf("azure:///subscriptions/123/resourceGroups/my-rg/providers/Microsoft.Compute/virtualMachineScaleSets/my-vmss/virtualMachines/%d", index), 1262 }, 1263 Status: infrav1exp.AzureMachinePoolMachineStatus{ 1264 Ready: true, 1265 ProvisioningState: ptr.To(infrav1.Succeeded), 1266 }, 1267 } 1268 } 1269 1270 func getAzureMachinePoolMachineWithOwnerMachine(index int) (clusterv1.Machine, infrav1exp.AzureMachinePoolMachine) { 1271 ampm := getAzureMachinePoolMachine(index) 1272 machine := clusterv1.Machine{ 1273 ObjectMeta: metav1.ObjectMeta{ 1274 Name: fmt.Sprintf("mpm%d", index), 1275 Namespace: "default", 1276 OwnerReferences: []metav1.OwnerReference{ 1277 { 1278 Name: "mp", 1279 Kind: "MachinePool", 1280 APIVersion: expv1.GroupVersion.String(), 1281 }, 1282 }, 1283 Labels: map[string]string{ 1284 clusterv1.ClusterNameLabel: "cluster1", 1285 clusterv1.MachinePoolNameLabel: "mp1", 1286 }, 1287 }, 1288 Spec: clusterv1.MachineSpec{ 1289 ProviderID: &m.Spec.ProviderID, 1290 InfrastructureRef: corev1.ObjectReference{ 1291 Kind: "AzureMachinePoolMachine", 1292 Name: ampm.Name, 1293 Namespace: ampm.Namespace, 1294 }, 1295 }, 1296 } 1297 1298 ampm.OwnerReferences = append(ampm.OwnerReferences, metav1.OwnerReference{ 1299 Name: machine.Name, 1300 Kind: "Machine", 1301 APIVersion: clusterv1.GroupVersion.String(), 1302 }) 1303 1304 return machine, ampm 1305 } 1306 1307 func TestMachinePoolScope_SetInfrastructureMachineKind(t *testing.T) { 1308 testcases := []struct { 1309 name string 1310 azureMachinePool infrav1exp.AzureMachinePool 1311 updated bool 1312 }{ 1313 { 1314 name: "should set infrastructure machine kind", 1315 azureMachinePool: infrav1exp.AzureMachinePool{ 1316 Status: infrav1exp.AzureMachinePoolStatus{}, 1317 }, 1318 updated: true, 1319 }, 1320 { 1321 name: "already set infrastructure machine kind", 1322 azureMachinePool: infrav1exp.AzureMachinePool{ 1323 Status: infrav1exp.AzureMachinePoolStatus{ 1324 InfrastructureMachineKind: infrav1exp.AzureMachinePoolMachineKind, 1325 }, 1326 }, 1327 updated: false, 1328 }, 1329 } 1330 1331 for _, tt := range testcases { 1332 tt := tt 1333 t.Run(tt.name, func(t *testing.T) { 1334 g := NewWithT(t) 1335 1336 machinePoolScope := &MachinePoolScope{ 1337 AzureMachinePool: &tt.azureMachinePool, 1338 } 1339 1340 got := machinePoolScope.SetInfrastructureMachineKind() 1341 g.Expect(machinePoolScope.AzureMachinePool.Status.InfrastructureMachineKind).To(Equal(infrav1exp.AzureMachinePoolMachineKind)) 1342 g.Expect(got).To(Equal(tt.updated)) 1343 }) 1344 } 1345 } 1346 1347 func TestMachinePoolScope_applyAzureMachinePoolMachines(t *testing.T) { 1348 ctx, cancel := context.WithCancel(context.Background()) 1349 defer cancel() 1350 scheme := runtime.NewScheme() 1351 _ = clusterv1.AddToScheme(scheme) 1352 _ = infrav1exp.AddToScheme(scheme) 1353 1354 tests := []struct { 1355 Name string 1356 Setup func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmssState *azure.VMSS, cb *fake.ClientBuilder) 1357 Verify func(g *WithT, amp *infrav1exp.AzureMachinePool, c client.Client, err error) 1358 }{ 1359 { 1360 Name: "if MachinePool is externally managed and overProvisionCount > 0, do not try to reduce replicas", 1361 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmssState *azure.VMSS, cb *fake.ClientBuilder) { 1362 mp.Annotations = map[string]string{clusterv1.ReplicasManagedByAnnotation: "cluster-autoscaler"} 1363 mp.Spec.Replicas = ptr.To[int32](1) 1364 1365 mpm1, ampm1 := getAzureMachinePoolMachineWithOwnerMachine(1) 1366 mpm2, ampm2 := getAzureMachinePoolMachineWithOwnerMachine(2) 1367 objects := []client.Object{} 1368 objects = append(objects, &mpm1, &m1, &mpm2, &m2) 1369 cb.WithObjects(objects...) 1370 vmssState.Instances = []azure.VMSSVM{ 1371 { 1372 ID: "/subscriptions/123/resourceGroups/my-rg/providers/Microsoft.Compute/virtualMachineScaleSets/my-vmss/virtualMachines/1", 1373 Name: "ampm1", 1374 }, 1375 { 1376 ID: "/subscriptions/123/resourceGroups/my-rg/providers/Microsoft.Compute/virtualMachineScaleSets/my-vmss/virtualMachines/2", 1377 Name: "ampm2", 1378 }, 1379 } 1380 }, 1381 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, c client.Client, err error) { 1382 g.Expect(err).NotTo(HaveOccurred()) 1383 list := clusterv1.MachineList{} 1384 g.Expect(c.List(ctx, &list)).NotTo(HaveOccurred()) 1385 g.Expect(list.Items).Should(HaveLen(2)) 1386 }, 1387 }, 1388 { 1389 Name: "if MachinePool is not externally managed and overProvisionCount > 0, reduce replicas", 1390 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmssState *azure.VMSS, cb *fake.ClientBuilder) { 1391 mp.Spec.Replicas = ptr.To[int32](1) 1392 1393 mpm1, ampm1 := getAzureMachinePoolMachineWithOwnerMachine(1) 1394 mpm2, ampm2 := getAzureMachinePoolMachineWithOwnerMachine(2) 1395 objects := []client.Object{} 1396 objects = append(objects, &mpm1, &m1, &mpm2, &m2) 1397 cb.WithObjects(objects...) 1398 1399 vmssState.Instances = []azure.VMSSVM{ 1400 { 1401 ID: "/subscriptions/123/resourceGroups/my-rg/providers/Microsoft.Compute/virtualMachineScaleSets/my-vmss/virtualMachines/1", 1402 Name: "ampm1", 1403 }, 1404 { 1405 ID: "/subscriptions/123/resourceGroups/my-rg/providers/Microsoft.Compute/virtualMachineScaleSets/my-vmss/virtualMachines/2", 1406 Name: "ampm2", 1407 }, 1408 } 1409 }, 1410 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, c client.Client, err error) { 1411 g.Expect(err).NotTo(HaveOccurred()) 1412 list := clusterv1.MachineList{} 1413 g.Expect(c.List(ctx, &list)).NotTo(HaveOccurred()) 1414 g.Expect(list.Items).Should(HaveLen(1)) 1415 }, 1416 }, 1417 { 1418 Name: "if existing MachinePool is not present, reduce replicas", 1419 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmssState *azure.VMSS, cb *fake.ClientBuilder) { 1420 mp.Spec.Replicas = ptr.To[int32](1) 1421 1422 vmssState.Instances = []azure.VMSSVM{ 1423 { 1424 ID: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm", 1425 Name: "vm", 1426 }, 1427 } 1428 }, 1429 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, c client.Client, err error) { 1430 g.Expect(err).NotTo(HaveOccurred()) 1431 list := infrav1exp.AzureMachinePoolMachineList{} 1432 g.Expect(c.List(ctx, &list)).NotTo(HaveOccurred()) 1433 g.Expect(list.Items).Should(HaveLen(1)) 1434 }, 1435 }, 1436 { 1437 Name: "if existing MachinePool is not present and Instances ID is in wrong format, reduce replicas", 1438 Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmssState *azure.VMSS, cb *fake.ClientBuilder) { 1439 mp.Spec.Replicas = ptr.To[int32](1) 1440 1441 vmssState.Instances = []azure.VMSSVM{ 1442 { 1443 ID: "foo/ampm0", 1444 Name: "ampm0", 1445 }, 1446 } 1447 }, 1448 Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, c client.Client, err error) { 1449 g.Expect(err).To(HaveOccurred()) 1450 }, 1451 }, 1452 } 1453 for _, tt := range tests { 1454 t.Run(tt.Name, func(t *testing.T) { 1455 var ( 1456 g = NewWithT(t) 1457 mockCtrl = gomock.NewController(t) 1458 cb = fake.NewClientBuilder().WithScheme(scheme) 1459 cluster = &clusterv1.Cluster{ 1460 ObjectMeta: metav1.ObjectMeta{ 1461 Name: "cluster1", 1462 Namespace: "default", 1463 }, 1464 Spec: clusterv1.ClusterSpec{ 1465 InfrastructureRef: &corev1.ObjectReference{ 1466 Name: "azCluster1", 1467 }, 1468 }, 1469 Status: clusterv1.ClusterStatus{ 1470 InfrastructureReady: true, 1471 }, 1472 } 1473 mp = &expv1.MachinePool{ 1474 ObjectMeta: metav1.ObjectMeta{ 1475 Name: "mp1", 1476 Namespace: "default", 1477 OwnerReferences: []metav1.OwnerReference{ 1478 { 1479 Name: "cluster1", 1480 Kind: "Cluster", 1481 APIVersion: clusterv1.GroupVersion.String(), 1482 }, 1483 }, 1484 }, 1485 } 1486 amp = &infrav1exp.AzureMachinePool{ 1487 ObjectMeta: metav1.ObjectMeta{ 1488 Name: "amp1", 1489 Namespace: "default", 1490 OwnerReferences: []metav1.OwnerReference{ 1491 { 1492 Name: "mp1", 1493 Kind: "MachinePool", 1494 APIVersion: expv1.GroupVersion.String(), 1495 }, 1496 }, 1497 }, 1498 } 1499 vmssState = &azure.VMSS{} 1500 ) 1501 defer mockCtrl.Finish() 1502 1503 tt.Setup(mp, amp, vmssState, cb.WithObjects(amp, cluster)) 1504 s := &MachinePoolScope{ 1505 client: cb.Build(), 1506 ClusterScoper: &ClusterScope{ 1507 Cluster: cluster, 1508 }, 1509 MachinePool: mp, 1510 AzureMachinePool: amp, 1511 vmssState: vmssState, 1512 } 1513 err := s.applyAzureMachinePoolMachines(ctx) 1514 tt.Verify(g, s.AzureMachinePool, s.client, err) 1515 }) 1516 } 1517 }