sigs.k8s.io/cluster-api-provider-aws@v1.5.5/controllers/awsmachine_controller_unit_test.go (about) 1 /* 2 Copyright 2019 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 "time" 26 27 "github.com/aws/aws-sdk-go/aws" 28 "github.com/golang/mock/gomock" 29 . "github.com/onsi/ginkgo" 30 . "github.com/onsi/gomega" 31 . "github.com/onsi/gomega/gstruct" 32 "github.com/pkg/errors" 33 corev1 "k8s.io/api/core/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/types" 36 "k8s.io/client-go/tools/record" 37 "k8s.io/klog/v2" 38 "k8s.io/klog/v2/klogr" 39 "k8s.io/utils/pointer" 40 ctrl "sigs.k8s.io/controller-runtime" 41 "sigs.k8s.io/controller-runtime/pkg/client" 42 "sigs.k8s.io/controller-runtime/pkg/client/fake" 43 "sigs.k8s.io/controller-runtime/pkg/reconcile" 44 45 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 46 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud" 47 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 48 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services" 49 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/mock_services" 50 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 51 "sigs.k8s.io/cluster-api/controllers/noderefutil" 52 capierrors "sigs.k8s.io/cluster-api/errors" 53 "sigs.k8s.io/cluster-api/util" 54 ) 55 56 const providerID = "aws:////myMachine" 57 58 func TestAWSMachineReconciler(t *testing.T) { 59 var ( 60 reconciler AWSMachineReconciler 61 cs *scope.ClusterScope 62 ms *scope.MachineScope 63 mockCtrl *gomock.Controller 64 ec2Svc *mock_services.MockEC2Interface 65 elbSvc *mock_services.MockELBInterface 66 secretSvc *mock_services.MockSecretInterface 67 objectStoreSvc *mock_services.MockObjectStoreInterface 68 recorder *record.FakeRecorder 69 ) 70 71 setup := func(t *testing.T, g *WithT, awsMachine *infrav1.AWSMachine) { 72 // https://github.com/kubernetes/klog/issues/87#issuecomment-540153080 73 // TODO: Replace with LogToOutput when https://github.com/kubernetes/klog/pull/99 merges 74 t.Helper() 75 76 var err error 77 78 if err := flag.Set("logtostderr", "false"); err != nil { 79 _ = fmt.Errorf("Error setting logtostderr flag") 80 } 81 if err := flag.Set("v", "2"); err != nil { 82 _ = fmt.Errorf("Error setting v flag") 83 } 84 klog.SetOutput(GinkgoWriter) 85 86 secret := &corev1.Secret{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Name: "bootstrap-data", 89 }, 90 Data: map[string][]byte{ 91 "value": []byte("shell-script"), 92 }, 93 } 94 95 secretIgnition := &corev1.Secret{ 96 ObjectMeta: metav1.ObjectMeta{ 97 Name: "bootstrap-data-ignition", 98 }, 99 Data: map[string][]byte{ 100 "value": []byte("ignitionJSON"), 101 "format": []byte("ignition"), 102 }, 103 } 104 105 client := fake.NewClientBuilder().WithObjects(awsMachine, secret, secretIgnition).Build() 106 ms, err = scope.NewMachineScope( 107 scope.MachineScopeParams{ 108 Client: client, 109 Cluster: &clusterv1.Cluster{ 110 ObjectMeta: metav1.ObjectMeta{ 111 Name: "test", 112 }, 113 Status: clusterv1.ClusterStatus{ 114 InfrastructureReady: true, 115 }, 116 }, 117 Machine: &clusterv1.Machine{ 118 ObjectMeta: metav1.ObjectMeta{ 119 Name: "test", 120 }, 121 Spec: clusterv1.MachineSpec{ 122 ClusterName: "capi-test", 123 Bootstrap: clusterv1.Bootstrap{ 124 DataSecretName: pointer.StringPtr("bootstrap-data"), 125 }, 126 }, 127 }, 128 InfraCluster: cs, 129 AWSMachine: awsMachine, 130 }, 131 ) 132 g.Expect(err).To(BeNil()) 133 134 cs, err = scope.NewClusterScope( 135 scope.ClusterScopeParams{ 136 Client: fake.NewClientBuilder().WithObjects(awsMachine, secret).Build(), 137 Cluster: &clusterv1.Cluster{}, 138 AWSCluster: &infrav1.AWSCluster{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, 139 }, 140 ) 141 g.Expect(err).To(BeNil()) 142 143 ms, err = scope.NewMachineScope( 144 scope.MachineScopeParams{ 145 Client: client, 146 Cluster: &clusterv1.Cluster{ 147 Status: clusterv1.ClusterStatus{ 148 InfrastructureReady: true, 149 }, 150 }, 151 Machine: &clusterv1.Machine{ 152 Spec: clusterv1.MachineSpec{ 153 ClusterName: "capi-test", 154 Bootstrap: clusterv1.Bootstrap{ 155 DataSecretName: pointer.StringPtr("bootstrap-data"), 156 }, 157 }, 158 }, 159 InfraCluster: cs, 160 AWSMachine: awsMachine, 161 }, 162 ) 163 g.Expect(err).To(BeNil()) 164 165 mockCtrl = gomock.NewController(t) 166 ec2Svc = mock_services.NewMockEC2Interface(mockCtrl) 167 secretSvc = mock_services.NewMockSecretInterface(mockCtrl) 168 elbSvc = mock_services.NewMockELBInterface(mockCtrl) 169 objectStoreSvc = mock_services.NewMockObjectStoreInterface(mockCtrl) 170 171 // If your test hangs for 9 minutes, increase the value here to the number of events during a reconciliation loop 172 recorder = record.NewFakeRecorder(2) 173 174 reconciler = AWSMachineReconciler{ 175 ec2ServiceFactory: func(scope.EC2Scope) services.EC2Interface { 176 return ec2Svc 177 }, 178 secretsManagerServiceFactory: func(cloud.ClusterScoper) services.SecretInterface { 179 return secretSvc 180 }, 181 objectStoreServiceFactory: func(cloud.ClusterScoper) services.ObjectStoreInterface { 182 return objectStoreSvc 183 }, 184 Recorder: recorder, 185 Log: klogr.New(), 186 } 187 } 188 teardown := func(t *testing.T, g *WithT) { 189 t.Helper() 190 mockCtrl.Finish() 191 } 192 193 t.Run("Reconciling an AWSMachine", func(t *testing.T) { 194 t.Run("when can't reach amazon", func(t *testing.T) { 195 expectedErr := errors.New("no connection available ") 196 runningInstance := func(t *testing.T, g *WithT) { 197 t.Helper() 198 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, expectedErr).AnyTimes() 199 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 200 } 201 202 t.Run("should exit immediately on an error state", func(t *testing.T) { 203 g := NewWithT(t) 204 awsMachine := getAWSMachine() 205 setup(t, g, awsMachine) 206 defer teardown(t, g) 207 runningInstance(t, g) 208 er := capierrors.CreateMachineError 209 ms.AWSMachine.Status.FailureReason = &er 210 ms.AWSMachine.Status.FailureMessage = pointer.StringPtr("Couldn't create machine") 211 212 buf := new(bytes.Buffer) 213 klog.SetOutput(buf) 214 215 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 216 g.Expect(buf).To(ContainSubstring("Error state detected, skipping reconciliation")) 217 }) 218 219 t.Run("should exit immediately if cluster infra isn't ready", func(t *testing.T) { 220 g := NewWithT(t) 221 awsMachine := getAWSMachine() 222 setup(t, g, awsMachine) 223 defer teardown(t, g) 224 runningInstance(t, g) 225 ms.Cluster.Status.InfrastructureReady = false 226 227 buf := new(bytes.Buffer) 228 klog.SetOutput(buf) 229 230 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 231 g.Expect(err).To(BeNil()) 232 g.Expect(buf.String()).To(ContainSubstring("Cluster infrastructure is not ready yet")) 233 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrav1.WaitingForClusterInfrastructureReason}}) 234 }) 235 236 t.Run("should exit immediately if bootstrap data secret reference isn't available", func(t *testing.T) { 237 g := NewWithT(t) 238 awsMachine := getAWSMachine() 239 setup(t, g, awsMachine) 240 defer teardown(t, g) 241 runningInstance(t, g) 242 ms.Machine.Spec.Bootstrap.DataSecretName = nil 243 244 buf := new(bytes.Buffer) 245 klog.SetOutput(buf) 246 247 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 248 249 g.Expect(err).To(BeNil()) 250 g.Expect(buf.String()).To(ContainSubstring("Bootstrap data secret reference is not yet available")) 251 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrav1.WaitingForBootstrapDataReason}}) 252 }) 253 254 t.Run("should return an error when we can't list instances by tags", func(t *testing.T) { 255 g := NewWithT(t) 256 awsMachine := getAWSMachine() 257 setup(t, g, awsMachine) 258 defer teardown(t, g) 259 runningInstance(t, g) 260 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 261 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 262 }) 263 264 t.Run("shouldn't add our finalizer to the machine", func(t *testing.T) { 265 g := NewWithT(t) 266 awsMachine := getAWSMachine() 267 setup(t, g, awsMachine) 268 defer teardown(t, g) 269 runningInstance(t, g) 270 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 271 272 g.Expect(len(ms.AWSMachine.Finalizers)).To(Equal(0)) 273 }) 274 }) 275 276 t.Run("when provider ID is populated correctly", func(t *testing.T) { 277 id := providerID 278 providerID := func(t *testing.T, g *WithT) { 279 t.Helper() 280 _, err := noderefutil.NewProviderID(id) 281 g.Expect(err).To(BeNil()) 282 283 ms.AWSMachine.Spec.ProviderID = &id 284 } 285 286 t.Run("should look up by provider ID when one exists", func(t *testing.T) { 287 g := NewWithT(t) 288 awsMachine := getAWSMachine() 289 setup(t, g, awsMachine) 290 defer teardown(t, g) 291 292 providerID(t, g) 293 expectedErr := errors.New("no connection available ") 294 ec2Svc.EXPECT().InstanceIfExists(PointsTo("myMachine")).Return(nil, expectedErr) 295 296 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 297 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 298 }) 299 300 t.Run("should fail to create instance and keep the finalizers as is", func(t *testing.T) { 301 g := NewWithT(t) 302 awsMachine := getAWSMachine() 303 setup(t, g, awsMachine) 304 defer teardown(t, g) 305 306 providerID(t, g) 307 expectedErr := errors.New("Invalid instance") 308 ec2Svc.EXPECT().InstanceIfExists(gomock.Any()).Return(nil, nil) 309 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 310 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, expectedErr) 311 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 312 313 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 314 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 315 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 316 }) 317 }) 318 319 t.Run("should fail to find instance if no provider ID provided", func(t *testing.T) { 320 g := NewWithT(t) 321 awsMachine := getAWSMachine() 322 setup(t, g, awsMachine) 323 defer teardown(t, g) 324 id := "aws////myMachine" 325 326 ms.AWSMachine.Spec.ProviderID = &id 327 expectedErr := "providerID must be of the form <cloudProvider>://<optional>/<segments>/<provider id>" 328 329 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 330 g.Expect(err.Error()).To(ContainSubstring(expectedErr)) 331 }) 332 333 t.Run("when instance creation succeeds", func(t *testing.T) { 334 var instance *infrav1.Instance 335 336 instanceCreate := func(t *testing.T, g *WithT) { 337 t.Helper() 338 339 instance = &infrav1.Instance{ 340 ID: "myMachine", 341 VolumeIDs: []string{"volume-1", "volume-2"}, 342 } 343 instance.State = infrav1.InstanceStatePending 344 345 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil) 346 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil) 347 } 348 349 t.Run("instance security group errors", func(t *testing.T) { 350 var buf *bytes.Buffer 351 getInstanceSecurityGroups := func(t *testing.T, g *WithT) { 352 t.Helper() 353 354 buf = new(bytes.Buffer) 355 klog.SetOutput(buf) 356 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(nil, errors.New("stop here")) 357 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 358 } 359 360 t.Run("should set attributes after creating an instance", func(t *testing.T) { 361 g := NewWithT(t) 362 awsMachine := getAWSMachine() 363 setup(t, g, awsMachine) 364 defer teardown(t, g) 365 instanceCreate(t, g) 366 getInstanceSecurityGroups(t, g) 367 368 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 369 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 370 g.Expect(ms.AWSMachine.Spec.ProviderID).To(PointTo(Equal(providerID))) 371 }) 372 373 t.Run("should set instance to pending", func(t *testing.T) { 374 g := NewWithT(t) 375 awsMachine := getAWSMachine() 376 setup(t, g, awsMachine) 377 defer teardown(t, g) 378 instanceCreate(t, g) 379 getInstanceSecurityGroups(t, g) 380 381 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 382 instance.State = infrav1.InstanceStatePending 383 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 384 g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStatePending))) 385 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(false)) 386 g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) 387 388 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 389 }) 390 391 t.Run("should set instance to running", func(t *testing.T) { 392 g := NewWithT(t) 393 awsMachine := getAWSMachine() 394 setup(t, g, awsMachine) 395 defer teardown(t, g) 396 397 instanceCreate(t, g) 398 getInstanceSecurityGroups(t, g) 399 400 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 401 instance.State = infrav1.InstanceStateRunning 402 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 403 g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateRunning))) 404 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(true)) 405 g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) 406 expectConditions(g, ms.AWSMachine, []conditionAssertion{ 407 {conditionType: infrav1.InstanceReadyCondition, status: corev1.ConditionTrue}, 408 }) 409 }) 410 }) 411 t.Run("new EC2 instance state: should error when the instance state is a new unseen one", func(t *testing.T) { 412 g := NewWithT(t) 413 awsMachine := getAWSMachine() 414 setup(t, g, awsMachine) 415 defer teardown(t, g) 416 instanceCreate(t, g) 417 418 buf := new(bytes.Buffer) 419 klog.SetOutput(buf) 420 instance.State = "NewAWSMachineState" 421 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 422 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 423 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 424 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 425 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(false)) 426 g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state is undefined"))) 427 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("InstanceUnhandledState"))) 428 g.Expect(ms.AWSMachine.Status.FailureMessage).To(PointTo(Equal("EC2 instance state \"NewAWSMachineState\" is undefined"))) 429 expectConditions(g, ms.AWSMachine, []conditionAssertion{{conditionType: infrav1.InstanceReadyCondition, status: corev1.ConditionUnknown}}) 430 }) 431 t.Run("security Groups succeed", func(t *testing.T) { 432 getCoreSecurityGroups := func(t *testing.T, g *WithT) { 433 t.Helper() 434 435 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()). 436 Return(map[string][]string{"eid": {}}, nil) 437 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 438 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil) 439 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 440 } 441 t.Run("should reconcile security groups", func(t *testing.T) { 442 g := NewWithT(t) 443 awsMachine := getAWSMachine() 444 setup(t, g, awsMachine) 445 defer teardown(t, g) 446 instanceCreate(t, g) 447 getCoreSecurityGroups(t, g) 448 449 ms.AWSMachine.Spec.AdditionalSecurityGroups = []infrav1.AWSResourceReference{ 450 { 451 ID: pointer.StringPtr("sg-2345"), 452 }, 453 } 454 ec2Svc.EXPECT().UpdateInstanceSecurityGroups(instance.ID, []string{"sg-2345"}) 455 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return([]string{"sg-2345"}, nil) 456 457 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 458 expectConditions(g, ms.AWSMachine, []conditionAssertion{{conditionType: infrav1.SecurityGroupsReadyCondition, status: corev1.ConditionTrue}}) 459 }) 460 461 t.Run("should not tag instances if there's no tags", func(t *testing.T) { 462 g := NewWithT(t) 463 awsMachine := getAWSMachine() 464 setup(t, g, awsMachine) 465 defer teardown(t, g) 466 instanceCreate(t, g) 467 getCoreSecurityGroups(t, g) 468 469 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 470 ec2Svc.EXPECT().UpdateInstanceSecurityGroups(gomock.Any(), gomock.Any()).Times(0) 471 if _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs); err != nil { 472 _ = fmt.Errorf("reconcileNormal reutrned an error during test") 473 } 474 }) 475 476 t.Run("should tag instances from machine and cluster tags", func(t *testing.T) { 477 g := NewWithT(t) 478 awsMachine := getAWSMachine() 479 setup(t, g, awsMachine) 480 defer teardown(t, g) 481 instanceCreate(t, g) 482 getCoreSecurityGroups(t, g) 483 484 ms.AWSMachine.Spec.AdditionalTags = infrav1.Tags{"kind": "alicorn"} 485 cs.AWSCluster.Spec.AdditionalTags = infrav1.Tags{"colour": "lavender"} 486 487 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 488 ec2Svc.EXPECT().UpdateResourceTags( 489 gomock.Any(), 490 map[string]string{ 491 "kind": "alicorn", 492 }, 493 map[string]string{}, 494 ).Return(nil).Times(2) 495 496 ec2Svc.EXPECT().UpdateResourceTags( 497 PointsTo("myMachine"), 498 map[string]string{ 499 "colour": "lavender", 500 "kind": "alicorn", 501 }, 502 map[string]string{}, 503 ).Return(nil) 504 505 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 506 g.Expect(err).To(BeNil()) 507 }) 508 t.Run("should tag instances volume tags", func(t *testing.T) { 509 g := NewWithT(t) 510 awsMachine := getAWSMachineWithAdditionalTags() 511 setup(t, g, awsMachine) 512 defer teardown(t, g) 513 instanceCreate(t, g) 514 getCoreSecurityGroups(t, g) 515 516 ms.AWSMachine.Spec.AdditionalTags = infrav1.Tags{"rootDeviceID": "id1", "rootDeviceSize": "30"} 517 518 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 519 ec2Svc.EXPECT().UpdateResourceTags( 520 gomock.Any(), 521 map[string]string{ 522 "rootDeviceID": "id1", 523 "rootDeviceSize": "30", 524 }, 525 map[string]string{}, 526 ).Return(nil).Times(3) 527 528 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 529 g.Expect(err).To(BeNil()) 530 }) 531 }) 532 533 t.Run("temporarily stopping then starting the AWSMachine(stateless)", func(t *testing.T) { 534 var buf *bytes.Buffer 535 getCoreSecurityGroups := func(t *testing.T, g *WithT) { 536 t.Helper() 537 538 buf = new(bytes.Buffer) 539 klog.SetOutput(buf) 540 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()). 541 Return(map[string][]string{"eid": {}}, nil).Times(1) 542 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 543 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 544 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 545 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 546 } 547 548 t.Run("should set instance to stopping and unready", func(t *testing.T) { 549 g := NewWithT(t) 550 awsMachine := getAWSMachine() 551 setup(t, g, awsMachine) 552 defer teardown(t, g) 553 instanceCreate(t, g) 554 getCoreSecurityGroups(t, g) 555 556 instance.State = infrav1.InstanceStateStopping 557 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 558 g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateStopping))) 559 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(false)) 560 g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) 561 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceStoppedReason}}) 562 }) 563 564 t.Run("should then set instance to stopped and unready", func(t *testing.T) { 565 g := NewWithT(t) 566 awsMachine := getAWSMachine() 567 setup(t, g, awsMachine) 568 defer teardown(t, g) 569 instanceCreate(t, g) 570 getCoreSecurityGroups(t, g) 571 572 instance.State = infrav1.InstanceStateStopped 573 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 574 g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateStopped))) 575 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(false)) 576 g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) 577 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceStoppedReason}}) 578 }) 579 580 t.Run("should then set instance to running and ready once it is restarted", func(t *testing.T) { 581 g := NewWithT(t) 582 awsMachine := getAWSMachine() 583 setup(t, g, awsMachine) 584 defer teardown(t, g) 585 instanceCreate(t, g) 586 getCoreSecurityGroups(t, g) 587 588 instance.State = infrav1.InstanceStateRunning 589 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 590 g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateRunning))) 591 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(true)) 592 g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) 593 }) 594 }) 595 t.Run("deleting the AWSMachine manually", func(t *testing.T) { 596 var buf *bytes.Buffer 597 deleteMachine := func(t *testing.T, g *WithT) { 598 t.Helper() 599 600 buf = new(bytes.Buffer) 601 klog.SetOutput(buf) 602 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 603 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 604 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 605 } 606 607 t.Run("should warn if an instance is shutting-down", func(t *testing.T) { 608 g := NewWithT(t) 609 awsMachine := getAWSMachine() 610 setup(t, g, awsMachine) 611 defer teardown(t, g) 612 instanceCreate(t, g) 613 deleteMachine(t, g) 614 instance.State = infrav1.InstanceStateShuttingDown 615 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 616 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(false)) 617 g.Expect(buf.String()).To(ContainSubstring(("Unexpected EC2 instance termination"))) 618 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("UnexpectedTermination"))) 619 }) 620 621 t.Run("should error when the instance is seen as terminated", func(t *testing.T) { 622 g := NewWithT(t) 623 awsMachine := getAWSMachine() 624 setup(t, g, awsMachine) 625 defer teardown(t, g) 626 instanceCreate(t, g) 627 deleteMachine(t, g) 628 629 instance.State = infrav1.InstanceStateTerminated 630 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 631 g.Expect(ms.AWSMachine.Status.Ready).To(Equal(false)) 632 g.Expect(buf.String()).To(ContainSubstring(("Unexpected EC2 instance termination"))) 633 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("UnexpectedTermination"))) 634 g.Expect(ms.AWSMachine.Status.FailureMessage).To(PointTo(Equal("EC2 instance state \"terminated\" is unexpected"))) 635 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceTerminatedReason}}) 636 }) 637 }) 638 t.Run("should not register if control plane ELB is already registered", func(t *testing.T) { 639 g := NewWithT(t) 640 awsMachine := getAWSMachine() 641 setup(t, g, awsMachine) 642 defer teardown(t, g) 643 instanceCreate(t, g) 644 645 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 646 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 647 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 648 return elbSvc 649 } 650 651 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(true, nil) 652 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 653 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 654 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 655 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 656 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 657 658 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 659 g.Expect(err).To(BeNil()) 660 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 661 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 662 }) 663 t.Run("should attach control plane ELB to instance", func(t *testing.T) { 664 g := NewWithT(t) 665 awsMachine := getAWSMachine() 666 setup(t, g, awsMachine) 667 defer teardown(t, g) 668 instanceCreate(t, g) 669 670 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 671 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 672 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 673 return elbSvc 674 } 675 676 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(false, nil) 677 elbSvc.EXPECT().RegisterInstanceWithAPIServerELB(gomock.Any()).Return(nil) 678 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 679 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 680 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 681 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 682 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 683 684 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 685 g.Expect(err).To(BeNil()) 686 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 687 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.ELBAttachedCondition, corev1.ConditionTrue, "", ""}}) 688 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 689 }) 690 t.Run("Should store userdata using AWS Secrets Manager", func(t *testing.T) { 691 g := NewWithT(t) 692 awsMachine := getAWSMachine() 693 setup(t, g, awsMachine) 694 defer teardown(t, g) 695 instanceCreate(t, g) 696 697 ms.AWSMachine.Spec.CloudInit.InsecureSkipSecretsManager = true 698 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 699 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 700 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 701 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 702 return elbSvc 703 } 704 705 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 706 g.Expect(err).To(BeNil()) 707 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 708 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 709 }) 710 t.Run("should fail to delete bootstrap data secret if AWSMachine state is updated", func(t *testing.T) { 711 g := NewWithT(t) 712 awsMachine := getAWSMachine() 713 setup(t, g, awsMachine) 714 defer teardown(t, g) 715 instanceCreate(t, g) 716 ms.Machine.Status.NodeRef = &corev1.ObjectReference{ 717 Namespace: "default", 718 Name: "test", 719 } 720 721 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 722 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 723 secretSvc.EXPECT().Delete(gomock.Any()).Return(errors.New("failed to delete entries from AWS Secret")).Times(1) 724 725 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 726 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 727 g.Expect(err).To(MatchError(ContainSubstring("failed to delete entries from AWS Secret"))) 728 }) 729 }) 730 t.Run("when instance creation fails", func(t *testing.T) { 731 var instance *infrav1.Instance 732 instanceCreate := func(t *testing.T, g *WithT) { 733 t.Helper() 734 instance = &infrav1.Instance{ 735 ID: "myMachine", 736 VolumeIDs: []string{"volume-1", "volume-2"}, 737 AvailabilityZone: "us-east-1", 738 } 739 instance.State = infrav1.InstanceStatePending 740 } 741 t.Run("Should fail while getting userdata", func(t *testing.T) { 742 expectedError := "failed to generate init script" 743 g := NewWithT(t) 744 awsMachine := getAWSMachine() 745 setup(t, g, awsMachine) 746 defer teardown(t, g) 747 instanceCreate(t, g) 748 749 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil) 750 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 751 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New(expectedError)).Times(1) 752 753 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 754 g.Expect(err.Error()).To(ContainSubstring(expectedError)) 755 756 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 757 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceProvisionFailedReason}}) 758 }) 759 t.Run("should fail to determine the registration status of control plane ELB", func(t *testing.T) { 760 g := NewWithT(t) 761 awsMachine := getAWSMachine() 762 setup(t, g, awsMachine) 763 defer teardown(t, g) 764 instanceCreate(t, g) 765 766 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 767 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 768 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 769 return elbSvc 770 } 771 772 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil) 773 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil) 774 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(false, errors.New("error describing ELB")) 775 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 776 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 777 778 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 779 g.Expect(err).ToNot(BeNil()) 780 g.Expect(err.Error()).To(ContainSubstring("error describing ELB")) 781 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 782 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("FailedAttachControlPlaneELB"))) 783 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 784 }) 785 t.Run("should fail to attach control plane ELB to instance", func(t *testing.T) { 786 g := NewWithT(t) 787 awsMachine := getAWSMachine() 788 setup(t, g, awsMachine) 789 defer teardown(t, g) 790 instanceCreate(t, g) 791 792 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 793 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 794 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 795 return elbSvc 796 } 797 798 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil) 799 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil) 800 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(false, nil) 801 elbSvc.EXPECT().RegisterInstanceWithAPIServerELB(gomock.Any()).Return(errors.New("failed to attach ELB")) 802 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) 803 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 804 805 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 806 g.Expect(err).ToNot(BeNil()) 807 g.Expect(err.Error()).To(ContainSubstring("failed to attach ELB")) 808 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("FailedAttachControlPlaneELB"))) 809 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 810 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 811 }) 812 t.Run("should fail to delete bootstrap data secret if AWSMachine is in failed state", func(t *testing.T) { 813 g := NewWithT(t) 814 awsMachine := getAWSMachine() 815 setup(t, g, awsMachine) 816 defer teardown(t, g) 817 ms.SetSecretPrefix("test") 818 ms.AWSMachine.Status.FailureReason = (*capierrors.MachineStatusError)(aws.String("error in AWSMachine")) 819 ms.SetSecretCount(0) 820 821 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 822 g.Expect(err).To(MatchError(ContainSubstring("secretPrefix present, but secretCount is not set"))) 823 }) 824 t.Run("Should fail in ensureTag", func(t *testing.T) { 825 id := providerID 826 ensureTag := func(t *testing.T, g *WithT) { 827 t.Helper() 828 ec2Svc.EXPECT().InstanceIfExists(gomock.Any()).Return(nil, nil) 829 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil) 830 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil) 831 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) 832 } 833 834 t.Run("Should fail to return machine annotations after instance is created", func(t *testing.T) { 835 g := NewWithT(t) 836 awsMachine := getAWSMachine() 837 setup(t, g, awsMachine) 838 defer teardown(t, g) 839 ms.AWSMachine.Spec.ProviderID = &id 840 841 instanceCreate(t, g) 842 ensureTag(t, g) 843 ms.AWSMachine.Annotations = map[string]string{TagsLastAppliedAnnotation: "12345"} 844 845 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 846 g.Expect(err.Error()).To(ContainSubstring("json: cannot unmarshal number into Go value of type map[string]interface {}")) 847 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 848 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 849 }) 850 t.Run("Should fail to update resource tags after instance is created", func(t *testing.T) { 851 g := NewWithT(t) 852 awsMachine := getAWSMachine() 853 setup(t, g, awsMachine) 854 defer teardown(t, g) 855 id := providerID 856 ms.AWSMachine.Spec.ProviderID = &id 857 858 instanceCreate(t, g) 859 ensureTag(t, g) 860 ms.AWSMachine.Annotations = map[string]string{TagsLastAppliedAnnotation: "{\"tag\":\"tag1\"}"} 861 862 ec2Svc.EXPECT().UpdateResourceTags(gomock.Any(), gomock.Any(), map[string]string{"tag": "tag1"}).Return(errors.New("failed to update resource tag")) 863 864 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 865 g.Expect(err).ToNot(BeNil()) 866 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 867 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) 868 }) 869 }) 870 t.Run("While ensuring SecurityGroups", func(t *testing.T) { 871 id := providerID 872 ensureSecurityGroups := func(t *testing.T, g *WithT) { 873 t.Helper() 874 ec2Svc.EXPECT().InstanceIfExists(gomock.Any()).Return(nil, nil) 875 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil) 876 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil) 877 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) 878 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil) 879 } 880 881 t.Run("Should fail to return machine annotations", func(t *testing.T) { 882 g := NewWithT(t) 883 awsMachine := getAWSMachine() 884 setup(t, g, awsMachine) 885 defer teardown(t, g) 886 id := providerID 887 ms.AWSMachine.Spec.ProviderID = &id 888 889 instanceCreate(t, g) 890 ensureSecurityGroups(t, g) 891 ms.AWSMachine.Annotations = map[string]string{SecurityGroupsLastAppliedAnnotation: "12345"} 892 893 ec2Svc.EXPECT().UpdateResourceTags(gomock.Any(), map[string]string{"tag": "\"old_tag\"\"\""}, gomock.Any()).Return(nil).AnyTimes() 894 895 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 896 g.Expect(err).ToNot(BeNil()) 897 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 898 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.SecurityGroupsReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.SecurityGroupsFailedReason}}) 899 }) 900 t.Run("Should fail to fetch core security groups", func(t *testing.T) { 901 g := NewWithT(t) 902 awsMachine := getAWSMachine() 903 setup(t, g, awsMachine) 904 defer teardown(t, g) 905 ms.AWSMachine.Spec.ProviderID = &id 906 907 instanceCreate(t, g) 908 ensureSecurityGroups(t, g) 909 ms.AWSMachine.Annotations = map[string]string{SecurityGroupsLastAppliedAnnotation: "{\"tag\":\"tag1\"}"} 910 911 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, errors.New("failed to get core security groups")).Times(1) 912 913 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 914 g.Expect(err).ToNot(BeNil()) 915 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 916 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.SecurityGroupsReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.SecurityGroupsFailedReason}}) 917 }) 918 t.Run("Should return silently if ensureSecurityGroups fails to fetch additional security groups", func(t *testing.T) { 919 g := NewWithT(t) 920 awsMachine := getAWSMachine() 921 setup(t, g, awsMachine) 922 defer teardown(t, g) 923 id := providerID 924 ms.AWSMachine.Spec.ProviderID = &id 925 926 instanceCreate(t, g) 927 ensureSecurityGroups(t, g) 928 ms.AWSMachine.Annotations = map[string]string{SecurityGroupsLastAppliedAnnotation: "{\"tag\":\"tag1\"}"} 929 ms.AWSMachine.Spec.AdditionalSecurityGroups = []infrav1.AWSResourceReference{ 930 { 931 Filters: []infrav1.Filter{ 932 { 933 Name: "example-name", 934 Values: []string{"example-value"}, 935 }, 936 }, 937 }, 938 } 939 940 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil) 941 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return([]string{"sg-1"}, errors.New("failed to get filtered SGs")) 942 943 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 944 g.Expect(err).To(BeNil()) 945 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 946 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.SecurityGroupsReadyCondition, corev1.ConditionTrue, "", ""}}) 947 }) 948 t.Run("Should fail to update security group", func(t *testing.T) { 949 g := NewWithT(t) 950 awsMachine := getAWSMachine() 951 setup(t, g, awsMachine) 952 defer teardown(t, g) 953 id := providerID 954 ms.AWSMachine.Spec.ProviderID = &id 955 956 instanceCreate(t, g) 957 ensureSecurityGroups(t, g) 958 ms.AWSMachine.Annotations = map[string]string{SecurityGroupsLastAppliedAnnotation: "{\"tag\":\"tag1\"}"} 959 ms.AWSMachine.Spec.AdditionalSecurityGroups = []infrav1.AWSResourceReference{ 960 { 961 Filters: []infrav1.Filter{ 962 { 963 Name: "id", 964 Values: []string{"sg-1"}, 965 }, 966 }, 967 }, 968 } 969 970 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil) 971 ec2Svc.EXPECT().UpdateInstanceSecurityGroups(gomock.Any(), gomock.Any()).Return(errors.New("failed to update security groups")) 972 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return([]string{"sg-1"}, nil) 973 974 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 975 g.Expect(err).ToNot(BeNil()) 976 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(infrav1.MachineFinalizer)) 977 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.SecurityGroupsReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.SecurityGroupsFailedReason}}) 978 }) 979 }) 980 }) 981 }) 982 983 t.Run("Secrets management lifecycle", func(t *testing.T) { 984 t.Run("Secrets management lifecycle when creating EC2 instances", func(t *testing.T) { 985 var instance *infrav1.Instance 986 secretPrefix := "test/secret" 987 988 t.Run("should leverage AWS Secrets Manager", func(t *testing.T) { 989 g := NewWithT(t) 990 awsMachine := getAWSMachine() 991 setup(t, g, awsMachine) 992 defer teardown(t, g) 993 994 instance = &infrav1.Instance{ 995 ID: "myMachine", 996 State: infrav1.InstanceStatePending, 997 } 998 999 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil).AnyTimes() 1000 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return(secretPrefix, int32(1), nil).Times(1) 1001 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil).AnyTimes() 1002 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 1003 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 1004 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1005 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1006 1007 ms.AWSMachine.ObjectMeta.Labels = map[string]string{ 1008 clusterv1.MachineControlPlaneLabelName: "", 1009 } 1010 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1011 }) 1012 }) 1013 1014 t.Run("Secrets management lifecycle when there's a node ref and a secret ARN", func(t *testing.T) { 1015 var instance *infrav1.Instance 1016 setNodeRef := func(t *testing.T, g *WithT) { 1017 t.Helper() 1018 1019 instance = &infrav1.Instance{ 1020 ID: "myMachine", 1021 } 1022 1023 ms.Machine.Status.NodeRef = &corev1.ObjectReference{ 1024 Kind: "Node", 1025 Name: "myMachine", 1026 APIVersion: "v1", 1027 } 1028 1029 ms.AWSMachine.Spec.CloudInit = infrav1.CloudInit{ 1030 SecretPrefix: "secret", 1031 SecretCount: 5, 1032 SecureSecretsBackend: infrav1.SecretBackendSecretsManager, 1033 } 1034 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(instance, nil).AnyTimes() 1035 } 1036 1037 t.Run("should delete the secret if the instance is running", func(t *testing.T) { 1038 g := NewWithT(t) 1039 awsMachine := getAWSMachine() 1040 setup(t, g, awsMachine) 1041 defer teardown(t, g) 1042 setNodeRef(t, g) 1043 1044 instance.State = infrav1.InstanceStateRunning 1045 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()). 1046 Return(map[string][]string{"eid": {}}, nil).Times(1) 1047 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1048 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1049 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1050 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1051 }) 1052 1053 t.Run("should delete the secret if the instance is terminated", func(t *testing.T) { 1054 g := NewWithT(t) 1055 awsMachine := getAWSMachine() 1056 setup(t, g, awsMachine) 1057 defer teardown(t, g) 1058 setNodeRef(t, g) 1059 1060 instance.State = infrav1.InstanceStateTerminated 1061 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1062 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1063 }) 1064 1065 t.Run("should delete the secret if the AWSMachine is deleted", func(t *testing.T) { 1066 g := NewWithT(t) 1067 awsMachine := getAWSMachine() 1068 setup(t, g, awsMachine) 1069 defer teardown(t, g) 1070 setNodeRef(t, g) 1071 1072 instance.State = infrav1.InstanceStateRunning 1073 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1074 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1075 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1076 }) 1077 1078 t.Run("should delete the secret if the AWSMachine is in a failure condition", func(t *testing.T) { 1079 g := NewWithT(t) 1080 awsMachine := getAWSMachine() 1081 setup(t, g, awsMachine) 1082 defer teardown(t, g) 1083 setNodeRef(t, g) 1084 1085 ms.AWSMachine.Status.FailureReason = capierrors.MachineStatusErrorPtr(capierrors.UpdateMachineError) 1086 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1087 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1088 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1089 }) 1090 t.Run("should not attempt to delete the secret if InsecureSkipSecretsManager is set on CloudInit", func(t *testing.T) { 1091 g := NewWithT(t) 1092 awsMachine := getAWSMachine() 1093 setup(t, g, awsMachine) 1094 defer teardown(t, g) 1095 setNodeRef(t, g) 1096 1097 ms.AWSMachine.Spec.CloudInit.InsecureSkipSecretsManager = true 1098 1099 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(0) 1100 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1101 1102 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1103 }) 1104 }) 1105 1106 t.Run("Secrets management lifecycle when there's only a secret ARN and no node ref", func(t *testing.T) { 1107 var instance *infrav1.Instance 1108 setSSM := func(t *testing.T, g *WithT) { 1109 t.Helper() 1110 1111 instance = &infrav1.Instance{ 1112 ID: "myMachine", 1113 } 1114 1115 ms.AWSMachine.Spec.CloudInit = infrav1.CloudInit{ 1116 SecretPrefix: "secret", 1117 SecretCount: 5, 1118 SecureSecretsBackend: infrav1.SecretBackendSecretsManager, 1119 } 1120 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(instance, nil).AnyTimes() 1121 } 1122 1123 t.Run("should not delete the secret if the instance is running", func(t *testing.T) { 1124 g := NewWithT(t) 1125 awsMachine := getAWSMachine() 1126 setup(t, g, awsMachine) 1127 defer teardown(t, g) 1128 setSSM(t, g) 1129 1130 instance.State = infrav1.InstanceStateRunning 1131 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()). 1132 Return(map[string][]string{"eid": {}}, nil).Times(1) 1133 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1134 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1135 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).MaxTimes(0) 1136 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1137 }) 1138 1139 t.Run("should delete the secret if the instance is terminated", func(t *testing.T) { 1140 g := NewWithT(t) 1141 awsMachine := getAWSMachine() 1142 setup(t, g, awsMachine) 1143 defer teardown(t, g) 1144 setSSM(t, g) 1145 1146 instance.State = infrav1.InstanceStateTerminated 1147 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1148 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1149 }) 1150 1151 t.Run("should delete the secret if the AWSMachine is deleted", func(t *testing.T) { 1152 g := NewWithT(t) 1153 awsMachine := getAWSMachine() 1154 setup(t, g, awsMachine) 1155 defer teardown(t, g) 1156 setSSM(t, g) 1157 1158 instance.State = infrav1.InstanceStateRunning 1159 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1160 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1161 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1162 }) 1163 1164 t.Run("should delete the secret if the AWSMachine is in a failure condition", func(t *testing.T) { 1165 g := NewWithT(t) 1166 awsMachine := getAWSMachine() 1167 setup(t, g, awsMachine) 1168 defer teardown(t, g) 1169 setSSM(t, g) 1170 1171 ms.AWSMachine.Status.FailureReason = capierrors.MachineStatusErrorPtr(capierrors.UpdateMachineError) 1172 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1173 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1174 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1175 }) 1176 }) 1177 1178 t.Run("Secrets management lifecycle when there is an intermittent connection issue and no secret could be stored", func(t *testing.T) { 1179 var instance *infrav1.Instance 1180 secretPrefix := "test/secret" 1181 1182 getInstances := func(t *testing.T, g *WithT) { 1183 t.Helper() 1184 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil).AnyTimes() 1185 } 1186 1187 t.Run("should error if secret could not be created", func(t *testing.T) { 1188 g := NewWithT(t) 1189 awsMachine := getAWSMachine() 1190 setup(t, g, awsMachine) 1191 defer teardown(t, g) 1192 getInstances(t, g) 1193 1194 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return(secretPrefix, int32(0), errors.New("connection error")).Times(1) 1195 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1196 g.Expect(err).ToNot(BeNil()) 1197 g.Expect(err.Error()).To(ContainSubstring("connection error")) 1198 g.Expect(ms.GetSecretPrefix()).To(Equal("prefix")) 1199 g.Expect(ms.GetSecretCount()).To(Equal(int32(1000))) 1200 }) 1201 t.Run("should update prefix and count on successful creation", func(t *testing.T) { 1202 g := NewWithT(t) 1203 awsMachine := getAWSMachine() 1204 setup(t, g, awsMachine) 1205 defer teardown(t, g) 1206 getInstances(t, g) 1207 1208 instance = &infrav1.Instance{ 1209 ID: "myMachine", 1210 } 1211 instance.State = infrav1.InstanceStatePending 1212 secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return(secretPrefix, int32(1), nil).Times(1) 1213 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil).AnyTimes() 1214 secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) 1215 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 1216 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1217 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1218 1219 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1220 1221 g.Expect(err).To(BeNil()) 1222 g.Expect(ms.GetSecretPrefix()).To(Equal(secretPrefix)) 1223 g.Expect(ms.GetSecretCount()).To(Equal(int32(1))) 1224 }) 1225 }) 1226 }) 1227 1228 t.Run("Object storage lifecycle", func(t *testing.T) { 1229 t.Run("creating EC2 instances", func(t *testing.T) { 1230 var instance *infrav1.Instance 1231 1232 getInstances := func(t *testing.T, g *WithT) { 1233 t.Helper() 1234 1235 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil).AnyTimes() 1236 } 1237 1238 useIgnition := func(t *testing.T, g *WithT) { 1239 t.Helper() 1240 1241 ms.Machine.Spec.Bootstrap.DataSecretName = pointer.StringPtr("bootstrap-data-ignition") 1242 ms.AWSMachine.Spec.CloudInit.SecretCount = 0 1243 ms.AWSMachine.Spec.CloudInit.SecretPrefix = "" 1244 } 1245 1246 t.Run("should leverage AWS S3", func(t *testing.T) { 1247 g := NewWithT(t) 1248 awsMachine := getAWSMachine() 1249 setup(t, g, awsMachine) 1250 defer teardown(t, g) 1251 getInstances(t, g) 1252 useIgnition(t, g) 1253 1254 instance = &infrav1.Instance{ 1255 ID: "myMachine", 1256 State: infrav1.InstanceStatePending, 1257 } 1258 fakeS3URL := "s3://foo" 1259 1260 objectStoreSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return(fakeS3URL, nil).Times(1) 1261 ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil).AnyTimes() 1262 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 1263 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1264 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1265 1266 ms.AWSMachine.ObjectMeta.Labels = map[string]string{ 1267 clusterv1.MachineControlPlaneLabelName: "", 1268 } 1269 1270 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1271 g.Expect(err).To(BeNil()) 1272 }) 1273 }) 1274 1275 t.Run("there's a node ref and a secret ARN", func(t *testing.T) { 1276 var instance *infrav1.Instance 1277 setNodeRef := func(t *testing.T, g *WithT) { 1278 t.Helper() 1279 1280 instance = &infrav1.Instance{ 1281 ID: "myMachine", 1282 } 1283 1284 ms.Machine.Status.NodeRef = &corev1.ObjectReference{ 1285 Kind: "Node", 1286 Name: "myMachine", 1287 APIVersion: "v1", 1288 } 1289 1290 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(instance, nil).AnyTimes() 1291 } 1292 useIgnition := func(t *testing.T, g *WithT) { 1293 t.Helper() 1294 1295 ms.Machine.Spec.Bootstrap.DataSecretName = pointer.StringPtr("bootstrap-data-ignition") 1296 ms.AWSMachine.Spec.CloudInit.SecretCount = 0 1297 ms.AWSMachine.Spec.CloudInit.SecretPrefix = "" 1298 } 1299 1300 t.Run("should delete the object if the instance is running", func(t *testing.T) { 1301 g := NewWithT(t) 1302 awsMachine := getAWSMachine() 1303 setup(t, g, awsMachine) 1304 defer teardown(t, g) 1305 setNodeRef(t, g) 1306 useIgnition(t, g) 1307 1308 instance.State = infrav1.InstanceStateRunning 1309 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 1310 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1311 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1312 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1313 1314 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1315 }) 1316 1317 t.Run("should delete the object if the instance is terminated", func(t *testing.T) { 1318 g := NewWithT(t) 1319 awsMachine := getAWSMachine() 1320 setup(t, g, awsMachine) 1321 defer teardown(t, g) 1322 setNodeRef(t, g) 1323 useIgnition(t, g) 1324 1325 instance.State = infrav1.InstanceStateTerminated 1326 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1327 1328 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1329 }) 1330 1331 t.Run("should delete the object if the instance is deleted", func(t *testing.T) { 1332 g := NewWithT(t) 1333 awsMachine := getAWSMachine() 1334 setup(t, g, awsMachine) 1335 defer teardown(t, g) 1336 setNodeRef(t, g) 1337 useIgnition(t, g) 1338 1339 instance.State = infrav1.InstanceStateRunning 1340 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1341 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1342 1343 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1344 }) 1345 1346 t.Run("should delete the object if the AWSMachine is in a failure condition", func(t *testing.T) { 1347 g := NewWithT(t) 1348 awsMachine := getAWSMachine() 1349 setup(t, g, awsMachine) 1350 defer teardown(t, g) 1351 setNodeRef(t, g) 1352 useIgnition(t, g) 1353 1354 // TODO: This seems to have no effect on the test result. 1355 ms.AWSMachine.Status.FailureReason = capierrors.MachineStatusErrorPtr(capierrors.UpdateMachineError) 1356 1357 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1358 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1359 1360 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1361 }) 1362 }) 1363 1364 t.Run("there's only a secret ARN and no node ref", func(t *testing.T) { 1365 var instance *infrav1.Instance 1366 1367 getInstances := func(t *testing.T, g *WithT) { 1368 t.Helper() 1369 1370 instance = &infrav1.Instance{ 1371 ID: "myMachine", 1372 } 1373 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(instance, nil).AnyTimes() 1374 } 1375 1376 useIgnition := func(t *testing.T, g *WithT) { 1377 t.Helper() 1378 1379 ms.Machine.Spec.Bootstrap.DataSecretName = pointer.StringPtr("bootstrap-data-ignition") 1380 ms.AWSMachine.Spec.CloudInit.SecretCount = 0 1381 ms.AWSMachine.Spec.CloudInit.SecretPrefix = "" 1382 } 1383 1384 t.Run("should not delete the object if the instance is running", func(t *testing.T) { 1385 g := NewWithT(t) 1386 awsMachine := getAWSMachine() 1387 setup(t, g, awsMachine) 1388 defer teardown(t, g) 1389 getInstances(t, g) 1390 1391 instance.State = infrav1.InstanceStateRunning 1392 ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1) 1393 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1) 1394 ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil) 1395 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).MaxTimes(0) 1396 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1397 }) 1398 1399 t.Run("should delete the object if the instance is terminated", func(t *testing.T) { 1400 g := NewWithT(t) 1401 awsMachine := getAWSMachine() 1402 setup(t, g, awsMachine) 1403 defer teardown(t, g) 1404 getInstances(t, g) 1405 useIgnition(t, g) 1406 1407 instance.State = infrav1.InstanceStateTerminated 1408 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1409 _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1410 }) 1411 1412 t.Run("should delete the object if the AWSMachine is deleted", func(t *testing.T) { 1413 g := NewWithT(t) 1414 awsMachine := getAWSMachine() 1415 setup(t, g, awsMachine) 1416 defer teardown(t, g) 1417 getInstances(t, g) 1418 useIgnition(t, g) 1419 1420 instance.State = infrav1.InstanceStateRunning 1421 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1422 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1423 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1424 }) 1425 1426 t.Run("should delete the object if the AWSMachine is in a failure condition", func(t *testing.T) { 1427 g := NewWithT(t) 1428 awsMachine := getAWSMachine() 1429 setup(t, g, awsMachine) 1430 defer teardown(t, g) 1431 getInstances(t, g) 1432 useIgnition(t, g) 1433 1434 // TODO: This seems to have no effect on the test result. 1435 ms.AWSMachine.Status.FailureReason = capierrors.MachineStatusErrorPtr(capierrors.UpdateMachineError) 1436 objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1) 1437 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil).AnyTimes() 1438 _, _ = reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1439 }) 1440 }) 1441 1442 t.Run("there is an intermittent connection issue and no object could be created", func(t *testing.T) { 1443 useIgnition := func(t *testing.T, g *WithT) { 1444 t.Helper() 1445 1446 ms.Machine.Spec.Bootstrap.DataSecretName = pointer.StringPtr("bootstrap-data-ignition") 1447 ms.AWSMachine.Spec.CloudInit.SecretCount = 0 1448 ms.AWSMachine.Spec.CloudInit.SecretPrefix = "" 1449 } 1450 1451 t.Run("should error if object could not be created", func(t *testing.T) { 1452 g := NewWithT(t) 1453 awsMachine := getAWSMachine() 1454 setup(t, g, awsMachine) 1455 defer teardown(t, g) 1456 useIgnition(t, g) 1457 1458 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil).AnyTimes() 1459 objectStoreSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("", errors.New("connection error")).Times(1) 1460 _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) 1461 g.Expect(err).ToNot(BeNil()) 1462 g.Expect(err.Error()).To(ContainSubstring("connection error")) 1463 }) 1464 }) 1465 }) 1466 1467 t.Run("Deleting an AWSMachine", func(t *testing.T) { 1468 finalizer := func(t *testing.T, g *WithT) { 1469 t.Helper() 1470 1471 ms.AWSMachine.Finalizers = []string{ 1472 infrav1.MachineFinalizer, 1473 metav1.FinalizerDeleteDependents, 1474 } 1475 } 1476 t.Run("should exit immediately on an error state", func(t *testing.T) { 1477 g := NewWithT(t) 1478 awsMachine := getAWSMachine() 1479 setup(t, g, awsMachine) 1480 defer teardown(t, g) 1481 finalizer(t, g) 1482 1483 expectedErr := errors.New("no connection available ") 1484 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, expectedErr).AnyTimes() 1485 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 1486 1487 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1488 g.Expect(errors.Cause(err)).To(MatchError(expectedErr)) 1489 }) 1490 t.Run("should log and remove finalizer when no machine exists", func(t *testing.T) { 1491 g := NewWithT(t) 1492 awsMachine := getAWSMachine() 1493 setup(t, g, awsMachine) 1494 defer teardown(t, g) 1495 finalizer(t, g) 1496 1497 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(nil, nil) 1498 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 1499 1500 buf := new(bytes.Buffer) 1501 klog.SetOutput(buf) 1502 1503 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1504 g.Expect(err).To(BeNil()) 1505 g.Expect(buf.String()).To(ContainSubstring("Unable to locate EC2 instance by ID or tags")) 1506 g.Expect(ms.AWSMachine.Finalizers).To(ConsistOf(metav1.FinalizerDeleteDependents)) 1507 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("NoInstanceFound"))) 1508 }) 1509 t.Run("should ignore instances in shutting down state", func(t *testing.T) { 1510 g := NewWithT(t) 1511 awsMachine := getAWSMachine() 1512 setup(t, g, awsMachine) 1513 defer teardown(t, g) 1514 finalizer(t, g) 1515 1516 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ 1517 State: infrav1.InstanceStateShuttingDown, 1518 }, nil) 1519 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 1520 1521 buf := new(bytes.Buffer) 1522 klog.SetOutput(buf) 1523 1524 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1525 g.Expect(err).To(BeNil()) 1526 g.Expect(buf.String()).To(ContainSubstring("EC2 instance is shutting down or already terminated")) 1527 g.Expect(ms.AWSMachine.Finalizers).To(ConsistOf(metav1.FinalizerDeleteDependents)) 1528 }) 1529 t.Run("should ignore instances in terminated down state", func(t *testing.T) { 1530 g := NewWithT(t) 1531 awsMachine := getAWSMachine() 1532 setup(t, g, awsMachine) 1533 defer teardown(t, g) 1534 finalizer(t, g) 1535 1536 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ 1537 State: infrav1.InstanceStateTerminated, 1538 }, nil) 1539 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 1540 1541 buf := new(bytes.Buffer) 1542 klog.SetOutput(buf) 1543 1544 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1545 g.Expect(err).To(BeNil()) 1546 g.Expect(buf.String()).To(ContainSubstring("EC2 instance is shutting down or already terminated")) 1547 g.Expect(ms.AWSMachine.Finalizers).To(ConsistOf(metav1.FinalizerDeleteDependents)) 1548 }) 1549 t.Run("instance not shutting down yet", func(t *testing.T) { 1550 id := "aws:////myid" 1551 getRunningInstance := func(t *testing.T, g *WithT) { 1552 t.Helper() 1553 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ID: id}, nil) 1554 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 1555 } 1556 t.Run("should return an error when the instance can't be terminated", func(t *testing.T) { 1557 g := NewWithT(t) 1558 awsMachine := getAWSMachine() 1559 setup(t, g, awsMachine) 1560 defer teardown(t, g) 1561 finalizer(t, g) 1562 getRunningInstance(t, g) 1563 1564 expected := errors.New("can't reach AWS to terminate machine") 1565 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(expected) 1566 1567 buf := new(bytes.Buffer) 1568 klog.SetOutput(buf) 1569 1570 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1571 g.Expect(errors.Cause(err)).To(MatchError(expected)) 1572 g.Expect(buf.String()).To(ContainSubstring("Terminating EC2 instance")) 1573 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("FailedTerminate"))) 1574 }) 1575 t.Run("when instance can be shut down", func(t *testing.T) { 1576 terminateInstance := func(t *testing.T, g *WithT) { 1577 t.Helper() 1578 ec2Svc.EXPECT().TerminateInstanceAndWait(gomock.Any()).Return(nil) 1579 secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() 1580 } 1581 1582 t.Run("should error when it can't retrieve security groups if there are network interfaces", func(t *testing.T) { 1583 g := NewWithT(t) 1584 awsMachine := getAWSMachine() 1585 setup(t, g, awsMachine) 1586 defer teardown(t, g) 1587 finalizer(t, g) 1588 getRunningInstance(t, g) 1589 terminateInstance(t, g) 1590 1591 ms.AWSMachine.Spec.NetworkInterfaces = []string{ 1592 "eth0", 1593 "eth1", 1594 } 1595 expected := errors.New("can't reach AWS to list security groups") 1596 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return(nil, expected) 1597 1598 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1599 g.Expect(errors.Cause(err)).To(MatchError(expected)) 1600 }) 1601 1602 t.Run("should error when it can't detach a security group from an interface", func(t *testing.T) { 1603 g := NewWithT(t) 1604 awsMachine := getAWSMachine() 1605 setup(t, g, awsMachine) 1606 defer teardown(t, g) 1607 finalizer(t, g) 1608 getRunningInstance(t, g) 1609 terminateInstance(t, g) 1610 1611 ms.AWSMachine.Spec.NetworkInterfaces = []string{ 1612 "eth0", 1613 "eth1", 1614 } 1615 expected := errors.New("can't reach AWS to detach security group") 1616 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{"sg0", "sg1"}, nil) 1617 ec2Svc.EXPECT().DetachSecurityGroupsFromNetworkInterface(gomock.Any(), gomock.Any()).Return(expected) 1618 1619 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1620 g.Expect(errors.Cause(err)).To(MatchError(expected)) 1621 }) 1622 1623 t.Run("should detach all combinations of network interfaces", func(t *testing.T) { 1624 g := NewWithT(t) 1625 awsMachine := getAWSMachine() 1626 setup(t, g, awsMachine) 1627 defer teardown(t, g) 1628 finalizer(t, g) 1629 getRunningInstance(t, g) 1630 terminateInstance(t, g) 1631 1632 ms.AWSMachine.Spec.NetworkInterfaces = []string{ 1633 "eth0", 1634 "eth1", 1635 } 1636 groups := []string{"sg0", "sg1"} 1637 ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{"sg0", "sg1"}, nil) 1638 ec2Svc.EXPECT().DetachSecurityGroupsFromNetworkInterface(groups, "eth0").Return(nil) 1639 ec2Svc.EXPECT().DetachSecurityGroupsFromNetworkInterface(groups, "eth1").Return(nil) 1640 1641 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1642 g.Expect(err).To(BeNil()) 1643 }) 1644 1645 t.Run("should remove security groups", func(t *testing.T) { 1646 g := NewWithT(t) 1647 awsMachine := getAWSMachine() 1648 setup(t, g, awsMachine) 1649 defer teardown(t, g) 1650 finalizer(t, g) 1651 getRunningInstance(t, g) 1652 terminateInstance(t, g) 1653 1654 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1655 g.Expect(err).To(BeNil()) 1656 g.Expect(ms.AWSMachine.Finalizers).To(ConsistOf(metav1.FinalizerDeleteDependents)) 1657 }) 1658 1659 t.Run("should fail to detach control plane ELB from instance", func(t *testing.T) { 1660 g := NewWithT(t) 1661 awsMachine := getAWSMachine() 1662 setup(t, g, awsMachine) 1663 defer teardown(t, g) 1664 finalizer(t, g) 1665 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 1666 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 1667 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 1668 return elbSvc 1669 } 1670 1671 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ 1672 State: infrav1.InstanceStateTerminated, 1673 }, nil) 1674 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(false, errors.New("error describing ELB")) 1675 1676 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1677 g.Expect(err).ToNot(BeNil()) 1678 g.Expect(err.Error()).To(ContainSubstring("error describing ELB")) 1679 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1680 g.Eventually(recorder.Events).Should(Receive(ContainSubstring("FailedDetachControlPlaneELB"))) 1681 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.ELBAttachedCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, "DeletingFailed"}}) 1682 }) 1683 1684 t.Run("should not do anything if control plane ELB is already detached from instance", func(t *testing.T) { 1685 g := NewWithT(t) 1686 awsMachine := getAWSMachine() 1687 setup(t, g, awsMachine) 1688 defer teardown(t, g) 1689 finalizer(t, g) 1690 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 1691 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 1692 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 1693 return elbSvc 1694 } 1695 1696 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ 1697 State: infrav1.InstanceStateTerminated, 1698 }, nil) 1699 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(false, nil) 1700 1701 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1702 g.Expect(err).To(BeNil()) 1703 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1704 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.ELBAttachedCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, clusterv1.DeletedReason}}) 1705 }) 1706 }) 1707 }) 1708 t.Run("Reconcile LB detachment", func(t *testing.T) { 1709 t.Run("should fail to determine registration status of ELB", func(t *testing.T) { 1710 g := NewWithT(t) 1711 awsMachine := getAWSMachine() 1712 setup(t, g, awsMachine) 1713 defer teardown(t, g) 1714 finalizer(t, g) 1715 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 1716 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 1717 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 1718 return elbSvc 1719 } 1720 1721 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ 1722 State: infrav1.InstanceStateTerminated, 1723 }, nil) 1724 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(true, nil) 1725 elbSvc.EXPECT().DeregisterInstanceFromAPIServerELB(gomock.Any()).Return(nil) 1726 1727 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1728 g.Expect(err).To(BeNil()) 1729 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1730 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.ELBAttachedCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, clusterv1.DeletedReason}}) 1731 }) 1732 t.Run("should fail to detach control plane ELB from instance", func(t *testing.T) { 1733 g := NewWithT(t) 1734 awsMachine := getAWSMachine() 1735 setup(t, g, awsMachine) 1736 defer teardown(t, g) 1737 finalizer(t, g) 1738 ms.Machine.Labels = map[string]string{clusterv1.MachineControlPlaneLabelName: ""} 1739 ms.AWSMachine.Status.InstanceState = &infrav1.InstanceStateStopping 1740 reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface { 1741 return elbSvc 1742 } 1743 1744 ec2Svc.EXPECT().GetRunningInstanceByTags(gomock.Any()).Return(&infrav1.Instance{ 1745 State: infrav1.InstanceStateTerminated, 1746 }, nil) 1747 elbSvc.EXPECT().IsInstanceRegisteredWithAPIServerELB(gomock.Any()).Return(true, nil) 1748 elbSvc.EXPECT().DeregisterInstanceFromAPIServerELB(gomock.Any()).Return(errors.New("Duplicate access point name for load balancer")) 1749 1750 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1751 g.Expect(err).ToNot(BeNil()) 1752 g.Expect(err.Error()).To(ContainSubstring("Duplicate access point name for load balancer")) 1753 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1754 expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.ELBAttachedCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, "DeletingFailed"}}) 1755 }) 1756 t.Run("should fail if secretPrefix present, but secretCount is not set", func(t *testing.T) { 1757 g := NewWithT(t) 1758 awsMachine := getAWSMachine() 1759 setup(t, g, awsMachine) 1760 defer teardown(t, g) 1761 finalizer(t, g) 1762 ms.SetSecretPrefix("test") 1763 ms.SetSecretCount(0) 1764 1765 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1766 g.Expect(err).To(MatchError(ContainSubstring("secretPrefix present, but secretCount is not set"))) 1767 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1768 }) 1769 t.Run("should fail if secrets backend is invalid", func(t *testing.T) { 1770 g := NewWithT(t) 1771 awsMachine := getAWSMachine() 1772 setup(t, g, awsMachine) 1773 defer teardown(t, g) 1774 finalizer(t, g) 1775 ms.AWSMachine.Spec.CloudInit.SecureSecretsBackend = "InvalidSecretBackend" 1776 1777 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1778 g.Expect(err).To(MatchError(ContainSubstring("invalid secret backend"))) 1779 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1780 }) 1781 t.Run("should fail if deleting entries from AWS Secret fails", func(t *testing.T) { 1782 g := NewWithT(t) 1783 awsMachine := getAWSMachine() 1784 setup(t, g, awsMachine) 1785 defer teardown(t, g) 1786 finalizer(t, g) 1787 ms.SetSecretPrefix("test") 1788 ms.SetSecretCount(1) 1789 secretSvc.EXPECT().Delete(gomock.Any()).Return(errors.New("Hierarchy Type Mismatch Exception")) 1790 1791 _, err := reconciler.reconcileDelete(ms, cs, cs, cs, cs) 1792 g.Expect(err).To(MatchError(ContainSubstring("Hierarchy Type Mismatch Exception"))) 1793 g.Expect(ms.AWSMachine.Finalizers).To(ContainElement(metav1.FinalizerDeleteDependents)) 1794 }) 1795 }) 1796 }) 1797 } 1798 1799 func TestAWSMachineReconciler_AWSClusterToAWSMachines(t *testing.T) { 1800 testCases := []struct { 1801 name string 1802 ownerCluster *clusterv1.Cluster 1803 awsCluster *infrav1.AWSCluster 1804 awsMachine *clusterv1.Machine 1805 requests []reconcile.Request 1806 }{ 1807 { 1808 name: "Should create reconcile request successfully", 1809 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-6"}}, 1810 awsMachine: &clusterv1.Machine{ 1811 ObjectMeta: metav1.ObjectMeta{ 1812 Name: "aws-test-6", 1813 Labels: map[string]string{ 1814 clusterv1.ClusterLabelName: "capi-test-6", 1815 }, 1816 }, 1817 Spec: clusterv1.MachineSpec{ 1818 ClusterName: "capi-test", 1819 InfrastructureRef: corev1.ObjectReference{ 1820 Kind: "AWSMachine", 1821 Name: "aws-machine-6", 1822 APIVersion: infrav1.GroupVersion.String(), 1823 }, 1824 }, 1825 }, 1826 awsCluster: &infrav1.AWSCluster{ 1827 ObjectMeta: metav1.ObjectMeta{ 1828 Name: "aws-test-6", 1829 OwnerReferences: []metav1.OwnerReference{ 1830 { 1831 Name: "capi-test-6", 1832 Kind: "Cluster", 1833 APIVersion: clusterv1.GroupVersion.String(), 1834 }, 1835 }, 1836 }, 1837 }, 1838 requests: []reconcile.Request{ 1839 { 1840 NamespacedName: types.NamespacedName{ 1841 Namespace: "default", 1842 Name: "aws-machine-6", 1843 }, 1844 }, 1845 }, 1846 }, 1847 { 1848 name: "Should not create reconcile request for deleted clusters", 1849 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", DeletionTimestamp: &metav1.Time{Time: time.Now()}}}, 1850 awsMachine: &clusterv1.Machine{ 1851 ObjectMeta: metav1.ObjectMeta{ 1852 Labels: map[string]string{ 1853 clusterv1.ClusterLabelName: "aws-test-1", 1854 }, 1855 Name: "aws-test-1", 1856 }, 1857 Spec: clusterv1.MachineSpec{ 1858 ClusterName: "capi-test", 1859 InfrastructureRef: corev1.ObjectReference{ 1860 Kind: "AWSMachine", 1861 Name: "aws-machine-1", 1862 APIVersion: infrav1.GroupVersion.String(), 1863 }, 1864 }, 1865 }, 1866 awsCluster: &infrav1.AWSCluster{ 1867 ObjectMeta: metav1.ObjectMeta{ 1868 Name: "aws-test-1", 1869 OwnerReferences: []metav1.OwnerReference{ 1870 { 1871 Name: "capi-test-1", 1872 Kind: "Cluster", 1873 APIVersion: clusterv1.GroupVersion.String(), 1874 }, 1875 }, 1876 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 1877 }, 1878 }, 1879 }, 1880 { 1881 name: "Should not create reconcile request if ownerCluster not found", 1882 awsMachine: &clusterv1.Machine{ 1883 ObjectMeta: metav1.ObjectMeta{ 1884 Labels: map[string]string{ 1885 clusterv1.ClusterLabelName: "aws-test-2", 1886 }, 1887 Name: "aws-test-2", 1888 }, 1889 Spec: clusterv1.MachineSpec{ 1890 ClusterName: "capi-test", 1891 InfrastructureRef: corev1.ObjectReference{ 1892 Kind: "AWSMachine", 1893 Name: "aws-machine-2", 1894 APIVersion: infrav1.GroupVersion.String(), 1895 }, 1896 }, 1897 }, 1898 awsCluster: &infrav1.AWSCluster{ 1899 ObjectMeta: metav1.ObjectMeta{ 1900 Name: "aws-test-2", 1901 OwnerReferences: []metav1.OwnerReference{ 1902 { 1903 Name: "capi-test-2", 1904 Kind: "Cluster", 1905 APIVersion: clusterv1.GroupVersion.String(), 1906 }, 1907 }, 1908 }, 1909 }, 1910 }, 1911 { 1912 name: "Should not create reconcile request if owned Machines not found", 1913 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-3"}}, 1914 awsMachine: &clusterv1.Machine{ 1915 ObjectMeta: metav1.ObjectMeta{ 1916 Name: "aws-test-3", 1917 }, 1918 Spec: clusterv1.MachineSpec{ 1919 ClusterName: "capi-test", 1920 InfrastructureRef: corev1.ObjectReference{ 1921 Kind: "AWSMachine", 1922 Name: "aws-machine-3", 1923 APIVersion: infrav1.GroupVersion.String(), 1924 }, 1925 }, 1926 }, 1927 awsCluster: &infrav1.AWSCluster{ 1928 ObjectMeta: metav1.ObjectMeta{ 1929 Name: "aws-test-3", 1930 OwnerReferences: []metav1.OwnerReference{ 1931 { 1932 Name: "capi-test-3", 1933 Kind: "Cluster", 1934 APIVersion: clusterv1.GroupVersion.String(), 1935 }, 1936 }, 1937 }, 1938 }, 1939 requests: []reconcile.Request{}, 1940 }, 1941 { 1942 name: "Should not create reconcile request if owned Machine type is not AWSMachine", 1943 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-4"}}, 1944 awsMachine: &clusterv1.Machine{ 1945 ObjectMeta: metav1.ObjectMeta{ 1946 Labels: map[string]string{ 1947 clusterv1.ClusterLabelName: "capi-test-4", 1948 }, 1949 Name: "aws-test-4", 1950 Namespace: "default", 1951 }, 1952 TypeMeta: metav1.TypeMeta{ 1953 Kind: "Machine", 1954 }, 1955 Spec: clusterv1.MachineSpec{ 1956 ClusterName: "capi-test", 1957 InfrastructureRef: corev1.ObjectReference{ 1958 Kind: "Machine", 1959 Name: "aws-machine-4", 1960 APIVersion: infrav1.GroupVersion.String(), 1961 }, 1962 }, 1963 }, 1964 awsCluster: &infrav1.AWSCluster{ 1965 ObjectMeta: metav1.ObjectMeta{ 1966 Name: "aws-test-4", 1967 OwnerReferences: []metav1.OwnerReference{ 1968 { 1969 Name: "capi-test-4", 1970 Kind: "Cluster", 1971 APIVersion: clusterv1.GroupVersion.String(), 1972 }, 1973 }, 1974 }, 1975 }, 1976 requests: []reconcile.Request{}, 1977 }, 1978 { 1979 name: "Should not create reconcile request if name for machine in infrastructure ref not found", 1980 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-5"}}, 1981 awsMachine: &clusterv1.Machine{ 1982 ObjectMeta: metav1.ObjectMeta{ 1983 Name: "aws-test-5", 1984 Labels: map[string]string{ 1985 clusterv1.ClusterLabelName: "capi-test-5", 1986 }, 1987 }, 1988 Spec: clusterv1.MachineSpec{ 1989 ClusterName: "capi-test", 1990 InfrastructureRef: corev1.ObjectReference{ 1991 Kind: "AWSMachine", 1992 APIVersion: infrav1.GroupVersion.String(), 1993 }, 1994 }, 1995 }, 1996 awsCluster: &infrav1.AWSCluster{ 1997 ObjectMeta: metav1.ObjectMeta{ 1998 Name: "aws-test-5", 1999 OwnerReferences: []metav1.OwnerReference{ 2000 { 2001 Name: "capi-test-5", 2002 Kind: "Cluster", 2003 APIVersion: clusterv1.GroupVersion.String(), 2004 }, 2005 }, 2006 }, 2007 }, 2008 requests: []reconcile.Request{}, 2009 }, 2010 } 2011 for _, tc := range testCases { 2012 t.Run(tc.name, func(t *testing.T) { 2013 g := NewWithT(t) 2014 reconciler := &AWSMachineReconciler{ 2015 Client: testEnv.Client, 2016 Log: klogr.New(), 2017 } 2018 ns, err := testEnv.CreateNamespace(ctx, fmt.Sprintf("namespace-%s", util.RandomString(5))) 2019 g.Expect(err).To(BeNil()) 2020 2021 createObject(g, tc.ownerCluster, ns.Name) 2022 defer cleanupObject(g, tc.ownerCluster) 2023 2024 createObject(g, tc.awsMachine, ns.Name) 2025 defer cleanupObject(g, tc.awsMachine) 2026 2027 tc.awsCluster.Namespace = ns.Name 2028 defer t.Cleanup(func() { 2029 g.Expect(testEnv.Cleanup(ctx, tc.awsCluster, ns)).To(Succeed()) 2030 }) 2031 2032 requests := reconciler.AWSClusterToAWSMachines(klogr.New())(tc.awsCluster) 2033 if tc.requests != nil { 2034 if len(tc.requests) > 0 { 2035 tc.requests[0].Namespace = ns.Name 2036 } 2037 g.Expect(requests).To(ConsistOf(tc.requests)) 2038 } else { 2039 g.Expect(requests).To(BeNil()) 2040 } 2041 }) 2042 } 2043 } 2044 2045 func TestAWSMachineReconciler_requeueAWSMachinesForUnpausedCluster(t *testing.T) { 2046 testCases := []struct { 2047 name string 2048 ownerCluster *clusterv1.Cluster 2049 requests []reconcile.Request 2050 }{ 2051 { 2052 name: "Should not create reconcile request for deleted clusters", 2053 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: "default", DeletionTimestamp: &metav1.Time{Time: time.Now()}}}, 2054 }, 2055 { 2056 name: "Should create reconcile request successfully", 2057 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: "default"}}, 2058 requests: []reconcile.Request{}, 2059 }, 2060 } 2061 for _, tc := range testCases { 2062 t.Run(tc.name, func(t *testing.T) { 2063 g := NewWithT(t) 2064 reconciler := &AWSMachineReconciler{ 2065 Client: testEnv.Client, 2066 Log: klogr.New(), 2067 } 2068 requests := reconciler.requeueAWSMachinesForUnpausedCluster(klogr.New())(tc.ownerCluster) 2069 if tc.requests != nil { 2070 g.Expect(requests).To(ConsistOf(tc.requests)) 2071 } else { 2072 g.Expect(requests).To(BeNil()) 2073 } 2074 }) 2075 } 2076 } 2077 2078 func TestAWSMachineReconciler_indexAWSMachineByInstanceID(t *testing.T) { 2079 t.Run("Should not return instance id if cluster type is not AWSCluster", func(t *testing.T) { 2080 g := NewWithT(t) 2081 reconciler := &AWSMachineReconciler{ 2082 Client: testEnv.Client, 2083 Log: klogr.New(), 2084 } 2085 machine := &clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: "default"}} 2086 requests := reconciler.indexAWSMachineByInstanceID(machine) 2087 g.Expect(requests).To(BeNil()) 2088 }) 2089 t.Run("Should return instance id successfully", func(t *testing.T) { 2090 g := NewWithT(t) 2091 reconciler := &AWSMachineReconciler{ 2092 Client: testEnv.Client, 2093 Log: klogr.New(), 2094 } 2095 awsMachine := &infrav1.AWSMachine{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: "default"}, Spec: infrav1.AWSMachineSpec{InstanceID: aws.String("12345")}} 2096 requests := reconciler.indexAWSMachineByInstanceID(awsMachine) 2097 g.Expect(requests).To(ConsistOf([]string{"12345"})) 2098 }) 2099 t.Run("Should not return instance id if instance id is not present", func(t *testing.T) { 2100 g := NewWithT(t) 2101 reconciler := &AWSMachineReconciler{ 2102 Client: testEnv.Client, 2103 Log: klogr.New(), 2104 } 2105 awsMachine := &infrav1.AWSMachine{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: "default"}} 2106 requests := reconciler.indexAWSMachineByInstanceID(awsMachine) 2107 g.Expect(requests).To(BeNil()) 2108 }) 2109 } 2110 2111 func TestAWSMachineReconciler_Reconcile(t *testing.T) { 2112 testCases := []struct { 2113 name string 2114 awsMachine *infrav1.AWSMachine 2115 ownerMachine *clusterv1.Machine 2116 ownerCluster *clusterv1.Cluster 2117 awsCluster *infrav1.AWSCluster 2118 expectError bool 2119 requeue bool 2120 }{ 2121 { 2122 name: "Should Reconcile successfully if no AWSMachine found", 2123 expectError: false, 2124 }, 2125 { 2126 name: "Should Reconcile AWSMachine with requeue", 2127 awsMachine: &infrav1.AWSMachine{ObjectMeta: metav1.ObjectMeta{Name: "aws-test-1"}, Spec: infrav1.AWSMachineSpec{InstanceType: "test"}}, 2128 requeue: true, 2129 expectError: false, 2130 }, 2131 { 2132 name: "Should fail Reconcile with GetOwnerMachine failure", 2133 awsMachine: &infrav1.AWSMachine{ 2134 ObjectMeta: metav1.ObjectMeta{ 2135 Name: "aws-test-2", 2136 OwnerReferences: []metav1.OwnerReference{ 2137 { 2138 APIVersion: clusterv1.GroupVersion.String(), 2139 Kind: "Machine", 2140 Name: "capi-test-machine", 2141 UID: "1", 2142 }, 2143 }, 2144 }, 2145 Spec: infrav1.AWSMachineSpec{InstanceType: "test"}, 2146 }, 2147 expectError: true, 2148 }, 2149 { 2150 name: "Should not Reconcile if machine does not contain cluster label", 2151 awsMachine: &infrav1.AWSMachine{ 2152 ObjectMeta: metav1.ObjectMeta{ 2153 Name: "aws-test-3", Annotations: map[string]string{clusterv1.PausedAnnotation: ""}, OwnerReferences: []metav1.OwnerReference{ 2154 { 2155 APIVersion: clusterv1.GroupVersion.String(), 2156 Kind: "Machine", 2157 Name: "capi-test-machine", 2158 UID: "1", 2159 }, 2160 }, 2161 }, Spec: infrav1.AWSMachineSpec{InstanceType: "test"}, 2162 }, 2163 ownerMachine: &clusterv1.Machine{ 2164 ObjectMeta: metav1.ObjectMeta{ 2165 Name: "capi-test-machine", 2166 }, 2167 Spec: clusterv1.MachineSpec{ 2168 ClusterName: "capi-test", 2169 }, 2170 }, 2171 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1"}}, 2172 expectError: false, 2173 }, 2174 { 2175 name: "Should not Reconcile if cluster is paused", 2176 awsMachine: &infrav1.AWSMachine{ 2177 ObjectMeta: metav1.ObjectMeta{ 2178 Name: "aws-test-4", Annotations: map[string]string{clusterv1.PausedAnnotation: ""}, OwnerReferences: []metav1.OwnerReference{ 2179 { 2180 APIVersion: clusterv1.GroupVersion.String(), 2181 Kind: "Machine", 2182 Name: "capi-test-machine", 2183 UID: "1", 2184 }, 2185 }, 2186 }, Spec: infrav1.AWSMachineSpec{InstanceType: "test"}, 2187 }, 2188 ownerMachine: &clusterv1.Machine{ 2189 ObjectMeta: metav1.ObjectMeta{ 2190 Labels: map[string]string{ 2191 clusterv1.ClusterLabelName: "capi-test-1", 2192 }, 2193 Name: "capi-test-machine", Namespace: "default", 2194 }, 2195 Spec: clusterv1.MachineSpec{ 2196 ClusterName: "capi-test", 2197 }, 2198 }, 2199 ownerCluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1"}}, 2200 expectError: false, 2201 }, 2202 { 2203 name: "Should not Reconcile if AWSManagedControlPlane is not ready", 2204 awsMachine: &infrav1.AWSMachine{ 2205 ObjectMeta: metav1.ObjectMeta{ 2206 Name: "aws-test-5", OwnerReferences: []metav1.OwnerReference{ 2207 { 2208 APIVersion: clusterv1.GroupVersion.String(), 2209 Kind: "Machine", 2210 Name: "capi-test-machine", 2211 UID: "1", 2212 }, 2213 }, 2214 }, Spec: infrav1.AWSMachineSpec{InstanceType: "test"}, 2215 }, 2216 ownerMachine: &clusterv1.Machine{ 2217 ObjectMeta: metav1.ObjectMeta{ 2218 Labels: map[string]string{ 2219 clusterv1.ClusterLabelName: "capi-test-1", 2220 }, 2221 Name: "capi-test-machine", Namespace: "default", 2222 }, Spec: clusterv1.MachineSpec{ 2223 ClusterName: "capi-test", 2224 }, 2225 }, 2226 ownerCluster: &clusterv1.Cluster{ 2227 ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1"}, 2228 Spec: clusterv1.ClusterSpec{ 2229 ControlPlaneRef: &corev1.ObjectReference{Kind: AWSManagedControlPlaneRefKind}, 2230 }, 2231 }, 2232 expectError: false, 2233 }, 2234 { 2235 name: "Should not Reconcile if AWSCluster is not ready", 2236 awsMachine: &infrav1.AWSMachine{ 2237 ObjectMeta: metav1.ObjectMeta{ 2238 Name: "aws-test-5", OwnerReferences: []metav1.OwnerReference{ 2239 { 2240 APIVersion: clusterv1.GroupVersion.String(), 2241 Kind: "Machine", 2242 Name: "capi-test-machine", 2243 UID: "1", 2244 }, 2245 }, 2246 }, Spec: infrav1.AWSMachineSpec{InstanceType: "test"}, 2247 }, 2248 ownerMachine: &clusterv1.Machine{ 2249 ObjectMeta: metav1.ObjectMeta{ 2250 Labels: map[string]string{ 2251 clusterv1.ClusterLabelName: "capi-test-1", 2252 }, 2253 Name: "capi-test-machine", Namespace: "default", 2254 }, 2255 Spec: clusterv1.MachineSpec{ 2256 ClusterName: "capi-test", 2257 }, 2258 }, 2259 ownerCluster: &clusterv1.Cluster{ 2260 ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1"}, 2261 Spec: clusterv1.ClusterSpec{ 2262 InfrastructureRef: &corev1.ObjectReference{Name: "aws-test-5"}, 2263 }, 2264 }, 2265 expectError: false, 2266 }, 2267 { 2268 name: "Should fail to reconcile while fetching infra cluster", 2269 awsMachine: &infrav1.AWSMachine{ 2270 ObjectMeta: metav1.ObjectMeta{ 2271 Name: "aws-test-5", OwnerReferences: []metav1.OwnerReference{ 2272 { 2273 APIVersion: clusterv1.GroupVersion.String(), 2274 Kind: "Machine", 2275 Name: "capi-test-machine", 2276 UID: "1", 2277 }, 2278 }, 2279 }, Spec: infrav1.AWSMachineSpec{InstanceType: "test"}, 2280 }, 2281 ownerMachine: &clusterv1.Machine{ 2282 ObjectMeta: metav1.ObjectMeta{ 2283 Labels: map[string]string{ 2284 clusterv1.ClusterLabelName: "capi-test-1", 2285 }, 2286 Name: "capi-test-machine", Namespace: "default", 2287 }, 2288 Spec: clusterv1.MachineSpec{ 2289 ClusterName: "capi-test", 2290 }, 2291 }, 2292 ownerCluster: &clusterv1.Cluster{ 2293 ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1"}, 2294 Spec: clusterv1.ClusterSpec{ 2295 InfrastructureRef: &corev1.ObjectReference{Name: "aws-test-5"}, 2296 }, 2297 }, 2298 awsCluster: &infrav1.AWSCluster{ObjectMeta: metav1.ObjectMeta{Name: "aws-test-5"}}, 2299 expectError: true, 2300 }, 2301 } 2302 2303 for _, tc := range testCases { 2304 t.Run(tc.name, func(t *testing.T) { 2305 g := NewWithT(t) 2306 reconciler := &AWSMachineReconciler{ 2307 Client: testEnv.Client, 2308 } 2309 ns, err := testEnv.CreateNamespace(ctx, fmt.Sprintf("namespace-%s", util.RandomString(5))) 2310 g.Expect(err).To(BeNil()) 2311 defer func() { 2312 g.Expect(testEnv.Cleanup(ctx, ns)).To(Succeed()) 2313 }() 2314 2315 createObject(g, tc.ownerCluster, ns.Name) 2316 defer cleanupObject(g, tc.ownerCluster) 2317 2318 createObject(g, tc.awsCluster, ns.Name) 2319 defer cleanupObject(g, tc.awsCluster) 2320 2321 createObject(g, tc.ownerMachine, ns.Name) 2322 defer cleanupObject(g, tc.ownerMachine) 2323 2324 createObject(g, tc.awsMachine, ns.Name) 2325 defer cleanupObject(g, tc.awsMachine) 2326 if tc.awsMachine != nil { 2327 g.Eventually(func() bool { 2328 machine := &infrav1.AWSMachine{} 2329 key := client.ObjectKey{ 2330 Name: tc.awsMachine.Name, 2331 Namespace: ns.Name, 2332 } 2333 err = testEnv.Get(ctx, key, machine) 2334 return err == nil 2335 }, 10*time.Second).Should(Equal(true)) 2336 2337 result, err := reconciler.Reconcile(ctx, ctrl.Request{ 2338 NamespacedName: client.ObjectKey{ 2339 Namespace: tc.awsMachine.Namespace, 2340 Name: tc.awsMachine.Name, 2341 }, 2342 }) 2343 if tc.expectError { 2344 g.Expect(err).ToNot(BeNil()) 2345 } else { 2346 g.Expect(err).To(BeNil()) 2347 } 2348 if tc.requeue { 2349 g.Expect(result.RequeueAfter).To(BeZero()) 2350 } 2351 } else { 2352 _, err = reconciler.Reconcile(ctx, ctrl.Request{ 2353 NamespacedName: client.ObjectKey{ 2354 Namespace: "default", 2355 Name: "test", 2356 }, 2357 }) 2358 g.Expect(err).To(BeNil()) 2359 } 2360 }) 2361 } 2362 } 2363 2364 func createObject(g *WithT, obj client.Object, namespace string) { 2365 if obj.DeepCopyObject() != nil { 2366 obj.SetNamespace(namespace) 2367 g.Expect(testEnv.Create(ctx, obj)).To(Succeed()) 2368 } 2369 } 2370 2371 func cleanupObject(g *WithT, obj client.Object) { 2372 if obj.DeepCopyObject() != nil { 2373 g.Expect(testEnv.Cleanup(ctx, obj)).To(Succeed()) 2374 } 2375 }