Skip to content

Commit 6814fc3

Browse files
Merge pull request #266 from bison/exists-provider-id
Bug 1761882: Search for existing instances by instance ID
2 parents 151ec6c + 238a8df commit 6814fc3

File tree

4 files changed

+195
-7
lines changed

4 files changed

+195
-7
lines changed

Diff for: pkg/actuators/machine/actuator.go

+18
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,24 @@ func (a *Actuator) getMachineInstances(cluster *clusterv1.Cluster, machine *mach
538538
return nil, err
539539
}
540540

541+
status := &providerconfigv1.AWSMachineProviderStatus{}
542+
err = a.codec.DecodeProviderStatus(machine.Status.ProviderStatus, status)
543+
544+
// If the status was decoded successfully, and there is a non-empty instance
545+
// ID, search using that, otherwise fallback to filtering based on tags.
546+
if err == nil && status.InstanceID != nil && *status.InstanceID != "" {
547+
i, err := getExistingInstanceByID(*status.InstanceID, client)
548+
549+
if err != nil {
550+
glog.Warningf("%s: Failed to find running instance by id %s: %v",
551+
machine.Name, *status.InstanceID, err)
552+
} else {
553+
glog.Infof("%s: Found instance by id: %s", machine.Name, *status.InstanceID)
554+
555+
return []*ec2.Instance{i}, nil
556+
}
557+
}
558+
541559
return getExistingInstances(machine, client)
542560
}
543561

Diff for: pkg/actuators/machine/actuator_test.go

+120
Original file line numberDiff line numberDiff line change
@@ -1135,3 +1135,123 @@ func TestCreate(t *testing.T) {
11351135
}
11361136
}
11371137
}
1138+
1139+
func TestGetMachineInstances(t *testing.T) {
1140+
clusterID := "aws-actuator-cluster"
1141+
instanceID := "i-02fa4197109214b46"
1142+
imageID := "ami-a9acbbd6"
1143+
1144+
machine, err := stubMachine()
1145+
if err != nil {
1146+
t.Fatalf("unable to build stub machine: %v", err)
1147+
}
1148+
1149+
codec, err := providerconfigv1.NewCodec()
1150+
if err != nil {
1151+
t.Fatalf("unable to build codec: %v", err)
1152+
}
1153+
1154+
testCases := []struct {
1155+
testcase string
1156+
providerStatus providerconfigv1.AWSMachineProviderStatus
1157+
awsClientFunc func(*gomock.Controller) awsclient.Client
1158+
}{
1159+
{
1160+
testcase: "empty-status-search-by-tag",
1161+
providerStatus: providerconfigv1.AWSMachineProviderStatus{},
1162+
awsClientFunc: func(ctrl *gomock.Controller) awsclient.Client {
1163+
mockAWSClient := mockaws.NewMockClient(ctrl)
1164+
1165+
request := &ec2.DescribeInstancesInput{
1166+
Filters: []*ec2.Filter{
1167+
{
1168+
Name: awsTagFilter("Name"),
1169+
Values: aws.StringSlice([]string{machine.Name}),
1170+
},
1171+
1172+
clusterFilter(clusterID),
1173+
1174+
{
1175+
Name: aws.String("instance-state-name"),
1176+
Values: existingInstanceStates(),
1177+
},
1178+
},
1179+
}
1180+
1181+
mockAWSClient.EXPECT().DescribeInstances(request).Return(
1182+
stubDescribeInstancesOutput(imageID, instanceID),
1183+
nil,
1184+
).Times(1)
1185+
1186+
return mockAWSClient
1187+
},
1188+
},
1189+
{
1190+
testcase: "has-status-search-by-id",
1191+
providerStatus: providerconfigv1.AWSMachineProviderStatus{
1192+
InstanceID: aws.String(instanceID),
1193+
},
1194+
awsClientFunc: func(ctrl *gomock.Controller) awsclient.Client {
1195+
mockAWSClient := mockaws.NewMockClient(ctrl)
1196+
1197+
request := &ec2.DescribeInstancesInput{
1198+
Filters: []*ec2.Filter{
1199+
{
1200+
Name: aws.String("instance-id"),
1201+
Values: aws.StringSlice([]string{instanceID}),
1202+
},
1203+
{
1204+
Name: aws.String("instance-state-name"),
1205+
Values: existingInstanceStates(),
1206+
},
1207+
},
1208+
}
1209+
1210+
mockAWSClient.EXPECT().DescribeInstances(request).Return(
1211+
stubDescribeInstancesOutput(imageID, instanceID),
1212+
nil,
1213+
).Times(1)
1214+
1215+
return mockAWSClient
1216+
},
1217+
},
1218+
}
1219+
1220+
for _, tc := range testCases {
1221+
t.Run(tc.testcase, func(t *testing.T) {
1222+
ctrl := gomock.NewController(t)
1223+
defer ctrl.Finish()
1224+
1225+
awsStatusRaw, err := codec.EncodeProviderStatus(&tc.providerStatus)
1226+
if err != nil {
1227+
t.Errorf("Error encoding ProviderStatus: %v", err)
1228+
}
1229+
1230+
machineCopy := machine.DeepCopy()
1231+
machineCopy.Status.ProviderStatus = awsStatusRaw
1232+
1233+
awsClient := tc.awsClientFunc(ctrl)
1234+
1235+
params := ActuatorParams{
1236+
Codec: codec,
1237+
AwsClientBuilder: awsClientBuilderFunc(awsClient),
1238+
}
1239+
1240+
actuator, err := NewActuator(params)
1241+
if err != nil {
1242+
t.Errorf("Error creating Actuator: %v", err)
1243+
}
1244+
1245+
_, err = actuator.getMachineInstances(nil, machineCopy)
1246+
if err != nil {
1247+
t.Errorf("Unexpected error from getMachineInstances: %v", err)
1248+
}
1249+
})
1250+
}
1251+
}
1252+
1253+
func awsClientBuilderFunc(c awsclient.Client) awsclient.AwsClientBuilderFuncType {
1254+
return func(_ client.Client, _, _, _ string) (awsclient.Client, error) {
1255+
return c, nil
1256+
}
1257+
}
File renamed without changes.

Diff for: pkg/actuators/machine/utils.go

+57-7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ import (
3333
awsclient "sigs.k8s.io/cluster-api-provider-aws/pkg/client"
3434
)
3535

36+
// existingInstanceStates returns the list of states an EC2 instance can be in
37+
// while being considered "existing", i.e. mostly anything but "Terminated".
38+
func existingInstanceStates() []*string {
39+
return []*string{
40+
aws.String(ec2.InstanceStateNameRunning),
41+
aws.String(ec2.InstanceStateNamePending),
42+
aws.String(ec2.InstanceStateNameStopped),
43+
aws.String(ec2.InstanceStateNameStopping),
44+
aws.String(ec2.InstanceStateNameShuttingDown),
45+
}
46+
}
47+
3648
// getRunningInstance returns the AWS instance for a given machine. If multiple instances match our machine,
3749
// the most recently launched will be returned. If no instance exists, an error will be returned.
3850
func getRunningInstance(machine *machinev1.Machine, client awsclient.Client) (*ec2.Instance, error) {
@@ -74,13 +86,51 @@ func getStoppedInstances(machine *machinev1.Machine, client awsclient.Client) ([
7486
}
7587

7688
func getExistingInstances(machine *machinev1.Machine, client awsclient.Client) ([]*ec2.Instance, error) {
77-
return getInstances(machine, client, []*string{
78-
aws.String(ec2.InstanceStateNameRunning),
79-
aws.String(ec2.InstanceStateNamePending),
80-
aws.String(ec2.InstanceStateNameStopped),
81-
aws.String(ec2.InstanceStateNameStopping),
82-
aws.String(ec2.InstanceStateNameShuttingDown),
83-
})
89+
return getInstances(machine, client, existingInstanceStates())
90+
}
91+
92+
func getExistingInstanceByID(id string, client awsclient.Client) (*ec2.Instance, error) {
93+
return getInstanceByID(id, client, existingInstanceStates())
94+
}
95+
96+
// getInstanceByID returns the instance with the given ID if it exists.
97+
func getInstanceByID(id string, client awsclient.Client, instanceStateFilter []*string) (*ec2.Instance, error) {
98+
if id == "" {
99+
return nil, fmt.Errorf("instance-id not specified")
100+
}
101+
102+
requestFilters := []*ec2.Filter{
103+
{
104+
Name: aws.String("instance-id"),
105+
Values: aws.StringSlice([]string{id}),
106+
},
107+
}
108+
109+
if instanceStateFilter != nil {
110+
requestFilters = append(requestFilters, &ec2.Filter{
111+
Name: aws.String("instance-state-name"),
112+
Values: instanceStateFilter,
113+
})
114+
}
115+
116+
request := &ec2.DescribeInstancesInput{Filters: requestFilters}
117+
118+
result, err := client.DescribeInstances(request)
119+
if err != nil {
120+
return nil, err
121+
}
122+
123+
if len(result.Reservations) != 1 {
124+
return nil, fmt.Errorf("found %d reservations for instance-id %s", len(result.Reservations), id)
125+
}
126+
127+
reservation := result.Reservations[0]
128+
129+
if len(reservation.Instances) != 1 {
130+
return nil, fmt.Errorf("found %d instances for instance-id %s", len(reservation.Instances), id)
131+
}
132+
133+
return reservation.Instances[0], nil
84134
}
85135

86136
// getInstances returns all instances that have a tag matching our machine name,

0 commit comments

Comments
 (0)