sigs.k8s.io/cluster-api-provider-aws@v1.5.5/exp/instancestate/awsinstancestate_controller_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package instancestate 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/aws/aws-sdk-go/aws" 26 "github.com/aws/aws-sdk-go/service/sqs" 27 "github.com/aws/aws-sdk-go/service/sqs/sqsiface" 28 "github.com/golang/mock/gomock" 29 . "github.com/onsi/gomega" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/utils/pointer" 33 ctrl "sigs.k8s.io/controller-runtime" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/controller" 36 37 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 38 "sigs.k8s.io/cluster-api-provider-aws/controllers" 39 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/instancestate/mock_sqsiface" 40 ) 41 42 func TestAWSInstanceStateController(t *testing.T) { 43 mockCtrl := gomock.NewController(t) 44 sqsSvs = mock_sqsiface.NewMockSQSAPI(mockCtrl) 45 instanceStateReconciler = &AwsInstanceStateReconciler{ 46 Client: testEnv.Client, 47 Log: ctrl.Log.WithName("controllers").WithName("AWSInstanceState"), 48 sqsServiceFactory: func() sqsiface.SQSAPI { 49 return sqsSvs 50 }, 51 } 52 defer mockCtrl.Finish() 53 54 t.Run("should maintain list of cluster queue URLs and reconcile failing machines", func(t *testing.T) { 55 g := NewWithT(t) 56 57 failingMachineMeta := metav1.ObjectMeta{ 58 Name: "aws-cluster-1-instance-1", 59 Namespace: "default", 60 } 61 sqsSvs.EXPECT().GetQueueUrl(&sqs.GetQueueUrlInput{QueueName: aws.String("aws-cluster-1-queue")}).AnyTimes(). 62 Return(&sqs.GetQueueUrlOutput{QueueUrl: aws.String("aws-cluster-1-url")}, nil) 63 sqsSvs.EXPECT().GetQueueUrl(&sqs.GetQueueUrlInput{QueueName: aws.String("aws-cluster-2-queue")}).AnyTimes(). 64 Return(&sqs.GetQueueUrlOutput{QueueUrl: aws.String("aws-cluster-2-url")}, nil) 65 sqsSvs.EXPECT().GetQueueUrl(&sqs.GetQueueUrlInput{QueueName: aws.String("aws-cluster-3-queue")}).AnyTimes(). 66 Return(&sqs.GetQueueUrlOutput{QueueUrl: aws.String("aws-cluster-3-url")}, nil) 67 sqsSvs.EXPECT().ReceiveMessage(&sqs.ReceiveMessageInput{QueueUrl: aws.String("aws-cluster-1-url")}).AnyTimes(). 68 DoAndReturn(func(arg *sqs.ReceiveMessageInput) (*sqs.ReceiveMessageOutput, error) { 69 m := &infrav1.AWSMachine{} 70 lookupKey := types.NamespacedName{ 71 Namespace: failingMachineMeta.Namespace, 72 Name: failingMachineMeta.Name, 73 } 74 err := k8sClient.Get(context.TODO(), lookupKey, m) 75 // start returning a message once the AWSMachine is available 76 if err == nil { 77 return &sqs.ReceiveMessageOutput{ 78 Messages: []*sqs.Message{{ 79 ReceiptHandle: aws.String("message-receipt-handle"), 80 Body: aws.String(messageBodyJSON), 81 }}, 82 }, nil 83 } 84 85 return &sqs.ReceiveMessageOutput{Messages: []*sqs.Message{}}, nil 86 }) 87 88 sqsSvs.EXPECT().ReceiveMessage(&sqs.ReceiveMessageInput{QueueUrl: aws.String("aws-cluster-2-url")}).AnyTimes(). 89 Return(&sqs.ReceiveMessageOutput{Messages: []*sqs.Message{}}, nil) 90 sqsSvs.EXPECT().ReceiveMessage(&sqs.ReceiveMessageInput{QueueUrl: aws.String("aws-cluster-3-url")}).AnyTimes(). 91 Return(&sqs.ReceiveMessageOutput{Messages: []*sqs.Message{}}, nil) 92 sqsSvs.EXPECT().DeleteMessage(&sqs.DeleteMessageInput{QueueUrl: aws.String("aws-cluster-1-url"), ReceiptHandle: aws.String("message-receipt-handle")}).AnyTimes(). 93 Return(nil, nil) 94 95 g.Expect(testEnv.Manager.GetFieldIndexer().IndexField(context.Background(), &infrav1.AWSMachine{}, 96 controllers.InstanceIDIndex, 97 func(o client.Object) []string { 98 m := o.(*infrav1.AWSMachine) 99 if m.Spec.InstanceID != nil { 100 return []string{*m.Spec.InstanceID} 101 } 102 return nil 103 }, 104 )).ToNot(HaveOccurred()) 105 106 err := instanceStateReconciler.SetupWithManager(context.Background(), testEnv.Manager, controller.Options{}) 107 g.Expect(err).ToNot(HaveOccurred()) 108 go func() { 109 fmt.Println("Starting the manager") 110 if err := testEnv.StartManager(ctx); err != nil { 111 panic(fmt.Sprintf("Failed to start the envtest manager: %v", err)) 112 } 113 }() 114 testEnv.WaitForWebhooks() 115 116 k8sClient = testEnv.GetClient() 117 118 persistObject(g, createAWSCluster("aws-cluster-1")) 119 persistObject(g, createAWSCluster("aws-cluster-2")) 120 121 machine1 := &infrav1.AWSMachine{ 122 Spec: infrav1.AWSMachineSpec{ 123 InstanceID: pointer.StringPtr("i-failing-instance-1"), 124 InstanceType: "test", 125 }, 126 ObjectMeta: failingMachineMeta, 127 } 128 persistObject(g, machine1) 129 130 t.Log("Ensuring queue URLs are up-to-date") 131 g.Eventually(func() bool { 132 exist := true 133 for _, cluster := range []string{"aws-cluster-1", "aws-cluster-2"} { 134 _, ok := instanceStateReconciler.queueURLs.Load(cluster) 135 exist = exist && ok 136 } 137 return exist 138 }, 10*time.Second).Should(Equal(true)) 139 140 deleteAWSCluster(g, "aws-cluster-2") 141 t.Log("Ensuring we stop tracking deleted queue") 142 g.Eventually(func() bool { 143 _, ok := instanceStateReconciler.queueURLs.Load("aws-cluster-2") 144 return ok 145 }, 10*time.Second).Should(Equal(false)) 146 147 persistObject(g, createAWSCluster("aws-cluster-3")) 148 t.Log("Ensuring newly created cluster is added to tracked clusters") 149 g.Eventually(func() bool { 150 exist := true 151 for _, cluster := range []string{"aws-cluster-1", "aws-cluster-3"} { 152 _, ok := instanceStateReconciler.queueURLs.Load(cluster) 153 exist = exist && ok 154 } 155 return exist 156 }, 10*time.Second).Should(Equal(true)) 157 158 t.Log("Ensuring machine is labelled with correct instance state") 159 g.Eventually(func() bool { 160 m := &infrav1.AWSMachine{} 161 key := types.NamespacedName{ 162 Namespace: failingMachineMeta.Namespace, 163 Name: failingMachineMeta.Name, 164 } 165 g.Expect(k8sClient.Get(context.TODO(), key, m)).NotTo(HaveOccurred()) 166 labels := m.GetLabels() 167 val := labels[Ec2InstanceStateLabelKey] 168 return val == "shutting-down" 169 }, 10*time.Second).Should(Equal(true)) 170 }) 171 } 172 173 const messageBodyJSON = `{ 174 "source": "aws.ec2", 175 "detail-type": "EC2 Instance State-change Notification", 176 "detail": { 177 "instance-id": "i-failing-instance-1", 178 "state": "shutting-down" 179 } 180 }`