sigs.k8s.io/cluster-api-provider-aws@v1.5.5/exp/controllers/awsmachinepool_controller_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 controllers 18 19 import ( 20 "bytes" 21 "context" 22 "flag" 23 "fmt" 24 "testing" 25 26 "github.com/go-logr/logr" 27 "github.com/golang/mock/gomock" 28 . "github.com/onsi/gomega" 29 "github.com/pkg/errors" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/client-go/tools/record" 34 "k8s.io/klog/v2" 35 "k8s.io/utils/pointer" 36 "sigs.k8s.io/controller-runtime/pkg/client/fake" 37 38 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 39 expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1" 40 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud" 41 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 42 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services" 43 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/mock_services" 44 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 45 "sigs.k8s.io/cluster-api/controllers/noderefutil" 46 capierrors "sigs.k8s.io/cluster-api/errors" 47 expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 48 "sigs.k8s.io/cluster-api/util/conditions" 49 "sigs.k8s.io/cluster-api/util/patch" 50 ) 51 52 func TestAWSMachinePoolReconciler(t *testing.T) { 53 var ( 54 reconciler AWSMachinePoolReconciler 55 cs *scope.ClusterScope 56 ms *scope.MachinePoolScope 57 mockCtrl *gomock.Controller 58 ec2Svc *mock_services.MockEC2Interface 59 asgSvc *mock_services.MockASGInterface 60 recorder *record.FakeRecorder 61 awsMachinePool *expinfrav1.AWSMachinePool 62 secret *corev1.Secret 63 ) 64 setup := func(t *testing.T, g *WithT) { 65 t.Helper() 66 67 var err error 68 69 if err := flag.Set("logtostderr", "false"); err != nil { 70 _ = fmt.Errorf("Error setting logtostderr flag") 71 } 72 if err := flag.Set("v", "2"); err != nil { 73 _ = fmt.Errorf("Error setting v flag") 74 } 75 ctx := context.TODO() 76 77 awsMachinePool = &expinfrav1.AWSMachinePool{ 78 ObjectMeta: metav1.ObjectMeta{ 79 Name: "test", 80 Namespace: "default", 81 }, 82 Spec: expinfrav1.AWSMachinePoolSpec{ 83 MinSize: int32(0), 84 MaxSize: int32(1), 85 }, 86 } 87 88 secret = &corev1.Secret{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Name: "bootstrap-data", 91 Namespace: "default", 92 }, 93 Data: map[string][]byte{ 94 "value": []byte("shell-script"), 95 }, 96 } 97 98 g.Expect(testEnv.Create(ctx, awsMachinePool)).To(Succeed()) 99 g.Expect(testEnv.Create(ctx, secret)).To(Succeed()) 100 101 ms, err = scope.NewMachinePoolScope( 102 scope.MachinePoolScopeParams{ 103 Client: testEnv.Client, 104 Cluster: &clusterv1.Cluster{ 105 Status: clusterv1.ClusterStatus{ 106 InfrastructureReady: true, 107 }, 108 }, 109 MachinePool: &expclusterv1.MachinePool{ 110 ObjectMeta: metav1.ObjectMeta{ 111 Name: "mp", 112 Namespace: "default", 113 }, 114 Spec: expclusterv1.MachinePoolSpec{ 115 ClusterName: "test", 116 Template: clusterv1.MachineTemplateSpec{ 117 Spec: clusterv1.MachineSpec{ 118 ClusterName: "test", 119 Bootstrap: clusterv1.Bootstrap{ 120 DataSecretName: pointer.StringPtr("bootstrap-data"), 121 }, 122 }, 123 }, 124 }, 125 }, 126 InfraCluster: cs, 127 AWSMachinePool: awsMachinePool, 128 }, 129 ) 130 g.Expect(err).To(BeNil()) 131 132 cs, err = setupCluster("test-cluster") 133 g.Expect(err).To(BeNil()) 134 135 mockCtrl = gomock.NewController(t) 136 ec2Svc = mock_services.NewMockEC2Interface(mockCtrl) 137 asgSvc = mock_services.NewMockASGInterface(mockCtrl) 138 139 // If the test hangs for 9 minutes, increase the value here to the number of events during a reconciliation loop 140 recorder = record.NewFakeRecorder(2) 141 142 reconciler = AWSMachinePoolReconciler{ 143 ec2ServiceFactory: func(scope.EC2Scope) services.EC2Interface { 144 return ec2Svc 145 }, 146 asgServiceFactory: func(cloud.ClusterScoper) services.ASGInterface { 147 return asgSvc 148 }, 149 Recorder: recorder, 150 } 151 } 152 153 teardown := func(t *testing.T, g *WithT) { 154 t.Helper() 155 156 ctx := context.TODO() 157 mpPh, err := patch.NewHelper(awsMachinePool, testEnv) 158 g.Expect(err).ShouldNot(HaveOccurred()) 159 awsMachinePool.SetFinalizers([]string{}) 160 g.Expect(mpPh.Patch(ctx, awsMachinePool)).To(Succeed()) 161 g.Expect(testEnv.Delete(ctx, awsMachinePool)).To(Succeed()) 162 g.Expect(testEnv.Delete(ctx, secret)).To(Succeed()) 163 mockCtrl.Finish() 164 } 165 166 t.Run("Reconciling an AWSMachinePool", func(t *testing.T) { 167 t.Run("when can't reach amazon", func(t *testing.T) { 168 expectedErr := errors.New("no connection available ") 169 getASG := func(t *testing.T, g *WithT) { 170 t.Helper() 171 172 ec2Svc.EXPECT().GetLaunchTemplate(gomock.Any()).Return(nil, "", expectedErr).AnyTimes() 173 asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(nil, expectedErr).AnyTimes() 174 } 175 t.Run("should exit immediately on an error state", func(t *testing.T) { 176 g := NewWithT(t) 177 setup(t, g) 178 defer teardown(t, g) 179 getASG(t, g) 180 181 er := capierrors.CreateMachineError 182 ms.AWSMachinePool.Status.FailureReason = &er 183 ms.AWSMachinePool.Status.FailureMessage = pointer.StringPtr("Couldn't create machine pool") 184 185 buf := new(bytes.Buffer) 186 klog.SetOutput(buf) 187 188 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs) 189 g.Expect(buf).To(ContainSubstring("Error state detected, skipping reconciliation")) 190 }) 191 t.Run("should add our finalizer to the machinepool", func(t *testing.T) { 192 g := NewWithT(t) 193 setup(t, g) 194 defer teardown(t, g) 195 getASG(t, g) 196 197 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs) 198 199 g.Expect(ms.AWSMachinePool.Finalizers).To(ContainElement(expinfrav1.MachinePoolFinalizer)) 200 }) 201 t.Run("should exit immediately if cluster infra isn't ready", func(t *testing.T) { 202 g := NewWithT(t) 203 setup(t, g) 204 defer teardown(t, g) 205 getASG(t, g) 206 207 ms.Cluster.Status.InfrastructureReady = false 208 209 buf := new(bytes.Buffer) 210 klog.SetOutput(buf) 211 212 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs) 213 g.Expect(err).To(BeNil()) 214 g.Expect(buf.String()).To(ContainSubstring("Cluster infrastructure is not ready yet")) 215 expectConditions(g, ms.AWSMachinePool, []conditionAssertion{{expinfrav1.ASGReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrav1.WaitingForClusterInfrastructureReason}}) 216 }) 217 t.Run("should exit immediately if bootstrap data secret reference isn't available", func(t *testing.T) { 218 g := NewWithT(t) 219 setup(t, g) 220 defer teardown(t, g) 221 getASG(t, g) 222 223 ms.MachinePool.Spec.Template.Spec.Bootstrap.DataSecretName = nil 224 buf := new(bytes.Buffer) 225 klog.SetOutput(buf) 226 227 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs) 228 229 g.Expect(err).To(BeNil()) 230 g.Expect(buf.String()).To(ContainSubstring("Bootstrap data secret reference is not yet available")) 231 expectConditions(g, ms.AWSMachinePool, []conditionAssertion{{expinfrav1.ASGReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrav1.WaitingForBootstrapDataReason}}) 232 }) 233 }) 234 t.Run("there's a provider ID", func(t *testing.T) { 235 id := "<cloudProvider>://<optional>/<segments>/<providerid>" 236 setProviderID := func(t *testing.T, g *WithT) { 237 t.Helper() 238 239 _, err := noderefutil.NewProviderID(id) 240 g.Expect(err).To(BeNil()) 241 242 ms.AWSMachinePool.Spec.ProviderID = id 243 } 244 t.Run("should look up by provider ID when one exists", func(t *testing.T) { 245 g := NewWithT(t) 246 setup(t, g) 247 defer teardown(t, g) 248 setProviderID(t, g) 249 250 expectedErr := errors.New("no connection available ") 251 var launchtemplate *expinfrav1.AWSLaunchTemplate 252 ec2Svc.EXPECT().GetLaunchTemplate(gomock.Any()).Return(launchtemplate, "", expectedErr) 253 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs) 254 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 255 }) 256 t.Run("should try to create a new machinepool if none exists", func(t *testing.T) { 257 g := NewWithT(t) 258 setup(t, g) 259 defer teardown(t, g) 260 setProviderID(t, g) 261 262 expectedErr := errors.New("Invalid instance") 263 asgSvc.EXPECT().ASGIfExists(gomock.Any()).Return(nil, nil).AnyTimes() 264 ec2Svc.EXPECT().GetLaunchTemplate(gomock.Any()).Return(nil, "", nil) 265 ec2Svc.EXPECT().DiscoverLaunchTemplateAMI(gomock.Any()).Return(nil, nil) 266 ec2Svc.EXPECT().CreateLaunchTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return("", expectedErr).AnyTimes() 267 268 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs) 269 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 270 }) 271 }) 272 273 t.Run("externally managed annotation", func(t *testing.T) { 274 g := NewWithT(t) 275 setup(t, g) 276 defer teardown(t, g) 277 278 asg := expinfrav1.AutoScalingGroup{ 279 Name: "an-asg", 280 DesiredCapacity: pointer.Int32(1), 281 } 282 asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(&asg, nil).AnyTimes() 283 asgSvc.EXPECT().UpdateASG(gomock.Any()).Return(nil).AnyTimes() 284 ec2Svc.EXPECT().GetLaunchTemplate(gomock.Any()).Return(nil, "", nil).AnyTimes() 285 ec2Svc.EXPECT().DiscoverLaunchTemplateAMI(gomock.Any()).Return(nil, nil).AnyTimes() 286 ec2Svc.EXPECT().CreateLaunchTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes() 287 ms.MachinePool.Annotations = map[string]string{ 288 scope.ReplicasManagedByAnnotation: scope.ExternalAutoscalerReplicasManagedByAnnotationValue, 289 } 290 ms.MachinePool.Spec.Replicas = pointer.Int32(0) 291 292 g.Expect(testEnv.Create(ctx, ms.MachinePool)).To(Succeed()) 293 294 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs) 295 g.Expect(*ms.MachinePool.Spec.Replicas).To(Equal(int32(1))) 296 }) 297 }) 298 299 t.Run("Deleting an AWSMachinePool", func(t *testing.T) { 300 finalizer := func(t *testing.T, g *WithT) { 301 t.Helper() 302 303 ms.AWSMachinePool.Finalizers = []string{ 304 expinfrav1.MachinePoolFinalizer, 305 metav1.FinalizerDeleteDependents, 306 } 307 } 308 t.Run("should exit immediately on an error state", func(t *testing.T) { 309 g := NewWithT(t) 310 setup(t, g) 311 defer teardown(t, g) 312 finalizer(t, g) 313 314 expectedErr := errors.New("no connection available ") 315 asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(nil, expectedErr).AnyTimes() 316 317 _, err := reconciler.reconcileDelete(ms, cs, cs) 318 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 319 }) 320 t.Run("should log and remove finalizer when no machinepool exists", func(t *testing.T) { 321 g := NewWithT(t) 322 setup(t, g) 323 defer teardown(t, g) 324 finalizer(t, g) 325 326 asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(nil, nil) 327 ec2Svc.EXPECT().GetLaunchTemplate(gomock.Any()).Return(nil, "", nil).AnyTimes() 328 329 buf := new(bytes.Buffer) 330 klog.SetOutput(buf) 331 332 _, err := reconciler.reconcileDelete(ms, cs, cs) 333 g.Expect(err).To(BeNil()) 334 g.Expect(buf.String()).To(ContainSubstring("Unable to locate ASG")) 335 g.Expect(ms.AWSMachinePool.Finalizers).To(ConsistOf(metav1.FinalizerDeleteDependents)) 336 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("NoASGFound"))) 337 }) 338 t.Run("should cause AWSMachinePool to go into NotReady", func(t *testing.T) { 339 g := NewWithT(t) 340 setup(t, g) 341 defer teardown(t, g) 342 finalizer(t, g) 343 344 inProgressASG := expinfrav1.AutoScalingGroup{ 345 Name: "an-asg-that-is-currently-being-deleted", 346 Status: expinfrav1.ASGStatusDeleteInProgress, 347 } 348 asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(&inProgressASG, nil) 349 ec2Svc.EXPECT().GetLaunchTemplate(gomock.Any()).Return(nil, "", nil).AnyTimes() 350 351 buf := new(bytes.Buffer) 352 klog.SetOutput(buf) 353 _, err := reconciler.reconcileDelete(ms, cs, cs) 354 g.Expect(err).To(BeNil()) 355 g.Expect(ms.AWSMachinePool.Status.Ready).To(Equal(false)) 356 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("DeletionInProgress"))) 357 }) 358 }) 359 } 360 361 //TODO: This was taken from awsmachine_controller_test, i think it should be moved to elsewhere in both locations like test/helpers 362 363 type conditionAssertion struct { 364 conditionType clusterv1.ConditionType 365 status corev1.ConditionStatus 366 severity clusterv1.ConditionSeverity 367 reason string 368 } 369 370 func expectConditions(g *WithT, m *expinfrav1.AWSMachinePool, expected []conditionAssertion) { 371 g.Expect(len(m.Status.Conditions)).To(BeNumerically(">=", len(expected)), "number of conditions") 372 for _, c := range expected { 373 actual := conditions.Get(m, c.conditionType) 374 g.Expect(actual).To(Not(BeNil())) 375 g.Expect(actual.Type).To(Equal(c.conditionType)) 376 g.Expect(actual.Status).To(Equal(c.status)) 377 g.Expect(actual.Severity).To(Equal(c.severity)) 378 g.Expect(actual.Reason).To(Equal(c.reason)) 379 } 380 } 381 382 func setupCluster(clusterName string) (*scope.ClusterScope, error) { 383 scheme := runtime.NewScheme() 384 _ = infrav1.AddToScheme(scheme) 385 awsCluster := &infrav1.AWSCluster{ 386 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 387 Spec: infrav1.AWSClusterSpec{}, 388 } 389 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(awsCluster).Build() 390 return scope.NewClusterScope(scope.ClusterScopeParams{ 391 Cluster: &clusterv1.Cluster{ 392 ObjectMeta: metav1.ObjectMeta{Name: clusterName}, 393 }, 394 AWSCluster: awsCluster, 395 Client: client, 396 }) 397 } 398 399 func Test_asgNeedsUpdates(t *testing.T) { 400 type args struct { 401 machinePoolScope *scope.MachinePoolScope 402 existingASG *expinfrav1.AutoScalingGroup 403 } 404 tests := []struct { 405 name string 406 args args 407 want bool 408 }{ 409 { 410 name: "replicas != asg.desiredCapacity", 411 args: args{ 412 machinePoolScope: &scope.MachinePoolScope{ 413 MachinePool: &expclusterv1.MachinePool{ 414 Spec: expclusterv1.MachinePoolSpec{ 415 Replicas: pointer.Int32(0), 416 }, 417 }, 418 }, 419 existingASG: &expinfrav1.AutoScalingGroup{ 420 DesiredCapacity: pointer.Int32(1), 421 }, 422 }, 423 want: true, 424 }, 425 { 426 name: "replicas (nil) != asg.desiredCapacity", 427 args: args{ 428 machinePoolScope: &scope.MachinePoolScope{ 429 MachinePool: &expclusterv1.MachinePool{ 430 Spec: expclusterv1.MachinePoolSpec{ 431 Replicas: nil, 432 }, 433 }, 434 }, 435 existingASG: &expinfrav1.AutoScalingGroup{ 436 DesiredCapacity: pointer.Int32(1), 437 }, 438 }, 439 want: true, 440 }, 441 { 442 name: "replicas != asg.desiredCapacity (nil)", 443 args: args{ 444 machinePoolScope: &scope.MachinePoolScope{ 445 MachinePool: &expclusterv1.MachinePool{ 446 Spec: expclusterv1.MachinePoolSpec{ 447 Replicas: pointer.Int32(0), 448 }, 449 }, 450 }, 451 existingASG: &expinfrav1.AutoScalingGroup{ 452 DesiredCapacity: nil, 453 }, 454 }, 455 want: true, 456 }, 457 { 458 name: "maxSize != asg.maxSize", 459 args: args{ 460 machinePoolScope: &scope.MachinePoolScope{ 461 MachinePool: &expclusterv1.MachinePool{ 462 Spec: expclusterv1.MachinePoolSpec{ 463 Replicas: pointer.Int32(1), 464 }, 465 }, 466 AWSMachinePool: &expinfrav1.AWSMachinePool{ 467 Spec: expinfrav1.AWSMachinePoolSpec{ 468 MaxSize: 1, 469 }, 470 }, 471 }, 472 existingASG: &expinfrav1.AutoScalingGroup{ 473 DesiredCapacity: pointer.Int32(1), 474 MaxSize: 2, 475 }, 476 }, 477 want: true, 478 }, 479 { 480 name: "minSize != asg.minSize", 481 args: args{ 482 machinePoolScope: &scope.MachinePoolScope{ 483 MachinePool: &expclusterv1.MachinePool{ 484 Spec: expclusterv1.MachinePoolSpec{ 485 Replicas: pointer.Int32(1), 486 }, 487 }, 488 AWSMachinePool: &expinfrav1.AWSMachinePool{ 489 Spec: expinfrav1.AWSMachinePoolSpec{ 490 MaxSize: 2, 491 MinSize: 0, 492 }, 493 }, 494 }, 495 existingASG: &expinfrav1.AutoScalingGroup{ 496 DesiredCapacity: pointer.Int32(1), 497 MaxSize: 2, 498 MinSize: 1, 499 }, 500 }, 501 want: true, 502 }, 503 { 504 name: "capacityRebalance != asg.capacityRebalance", 505 args: args{ 506 machinePoolScope: &scope.MachinePoolScope{ 507 MachinePool: &expclusterv1.MachinePool{ 508 Spec: expclusterv1.MachinePoolSpec{ 509 Replicas: pointer.Int32(1), 510 }, 511 }, 512 AWSMachinePool: &expinfrav1.AWSMachinePool{ 513 Spec: expinfrav1.AWSMachinePoolSpec{ 514 MaxSize: 2, 515 MinSize: 0, 516 CapacityRebalance: true, 517 }, 518 }, 519 }, 520 existingASG: &expinfrav1.AutoScalingGroup{ 521 DesiredCapacity: pointer.Int32(1), 522 MaxSize: 2, 523 MinSize: 0, 524 CapacityRebalance: false, 525 }, 526 }, 527 want: true, 528 }, 529 { 530 name: "MixedInstancesPolicy != asg.MixedInstancesPolicy", 531 args: args{ 532 machinePoolScope: &scope.MachinePoolScope{ 533 MachinePool: &expclusterv1.MachinePool{ 534 Spec: expclusterv1.MachinePoolSpec{ 535 Replicas: pointer.Int32(1), 536 }, 537 }, 538 AWSMachinePool: &expinfrav1.AWSMachinePool{ 539 Spec: expinfrav1.AWSMachinePoolSpec{ 540 MaxSize: 2, 541 MinSize: 0, 542 CapacityRebalance: true, 543 MixedInstancesPolicy: &expinfrav1.MixedInstancesPolicy{ 544 InstancesDistribution: &expinfrav1.InstancesDistribution{ 545 OnDemandAllocationStrategy: expinfrav1.OnDemandAllocationStrategyPrioritized, 546 }, 547 Overrides: nil, 548 }, 549 }, 550 }, 551 Logger: logr.Discard(), 552 }, 553 existingASG: &expinfrav1.AutoScalingGroup{ 554 DesiredCapacity: pointer.Int32(1), 555 MaxSize: 2, 556 MinSize: 0, 557 CapacityRebalance: true, 558 MixedInstancesPolicy: &expinfrav1.MixedInstancesPolicy{}, 559 }, 560 }, 561 want: true, 562 }, 563 { 564 name: "all matches", 565 args: args{ 566 machinePoolScope: &scope.MachinePoolScope{ 567 MachinePool: &expclusterv1.MachinePool{ 568 Spec: expclusterv1.MachinePoolSpec{ 569 Replicas: pointer.Int32(1), 570 }, 571 }, 572 AWSMachinePool: &expinfrav1.AWSMachinePool{ 573 Spec: expinfrav1.AWSMachinePoolSpec{ 574 MaxSize: 2, 575 MinSize: 0, 576 CapacityRebalance: true, 577 MixedInstancesPolicy: &expinfrav1.MixedInstancesPolicy{ 578 InstancesDistribution: &expinfrav1.InstancesDistribution{ 579 OnDemandAllocationStrategy: expinfrav1.OnDemandAllocationStrategyPrioritized, 580 }, 581 Overrides: nil, 582 }, 583 }, 584 }, 585 }, 586 existingASG: &expinfrav1.AutoScalingGroup{ 587 DesiredCapacity: pointer.Int32(1), 588 MaxSize: 2, 589 MinSize: 0, 590 CapacityRebalance: true, 591 MixedInstancesPolicy: &expinfrav1.MixedInstancesPolicy{ 592 InstancesDistribution: &expinfrav1.InstancesDistribution{ 593 OnDemandAllocationStrategy: expinfrav1.OnDemandAllocationStrategyPrioritized, 594 }, 595 Overrides: nil, 596 }, 597 }, 598 }, 599 want: false, 600 }, 601 { 602 name: "externally managed annotation ignores difference between desiredCapacity and replicas", 603 args: args{ 604 machinePoolScope: &scope.MachinePoolScope{ 605 MachinePool: &expclusterv1.MachinePool{ 606 ObjectMeta: metav1.ObjectMeta{ 607 Annotations: map[string]string{ 608 scope.ReplicasManagedByAnnotation: scope.ExternalAutoscalerReplicasManagedByAnnotationValue, 609 }, 610 }, 611 Spec: expclusterv1.MachinePoolSpec{ 612 Replicas: pointer.Int32(0), 613 }, 614 }, 615 AWSMachinePool: &expinfrav1.AWSMachinePool{ 616 Spec: expinfrav1.AWSMachinePoolSpec{}, 617 }, 618 }, 619 existingASG: &expinfrav1.AutoScalingGroup{ 620 DesiredCapacity: pointer.Int32(1), 621 }, 622 }, 623 want: false, 624 }, 625 { 626 name: "without externally managed annotation ignores difference between desiredCapacity and replicas", 627 args: args{ 628 machinePoolScope: &scope.MachinePoolScope{ 629 MachinePool: &expclusterv1.MachinePool{ 630 Spec: expclusterv1.MachinePoolSpec{ 631 Replicas: pointer.Int32(0), 632 }, 633 }, 634 AWSMachinePool: &expinfrav1.AWSMachinePool{ 635 Spec: expinfrav1.AWSMachinePoolSpec{}, 636 }, 637 }, 638 existingASG: &expinfrav1.AutoScalingGroup{ 639 DesiredCapacity: pointer.Int32(1), 640 }, 641 }, 642 want: true, 643 }, 644 } 645 for _, tt := range tests { 646 t.Run(tt.name, func(t *testing.T) { 647 g := NewWithT(t) 648 g.Expect(asgNeedsUpdates(tt.args.machinePoolScope, tt.args.existingASG)).To(Equal(tt.want)) 649 }) 650 } 651 }