Skip to content

Commit 36c0ac9

Browse files
authored
Merge pull request #1260 from kmala/fix
Fix the batched describe insance method for instance not found case
2 parents 8a0025b + f915f24 commit 36c0ac9

File tree

2 files changed

+147
-3
lines changed

2 files changed

+147
-3
lines changed

pkg/providers/v1/describe_instance_batch.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func (b *describeInstanceBatcher) DescribeInstances(ctx context.Context, input *
5757
return nil, fmt.Errorf("expected to receive a single instance only, found %d", len(input.InstanceIds))
5858
}
5959
result := b.batcher.Add(ctx, input)
60+
if result.Output == nil {
61+
return nil, result.Err
62+
}
6063
return []*ec2types.Instance{result.Output}, result.Err
6164
}
6265

@@ -74,7 +77,8 @@ func describeInstanceHasher(ctx context.Context, input *ec2.DescribeInstancesInp
7477
func execDescribeInstanceBatch(ec2api iface.EC2) batcher.BatchExecutor[ec2.DescribeInstancesInput, ec2types.Instance] {
7578
return func(ctx context.Context, inputs []*ec2.DescribeInstancesInput) []batcher.Result[ec2types.Instance] {
7679
results := make([]batcher.Result[ec2types.Instance], len(inputs))
77-
firstInput := inputs[0]
80+
81+
firstInput := *inputs[0]
7882
// aggregate instanceIDs into 1 input
7983
for _, input := range inputs[1:] {
8084
firstInput.InstanceIds = append(firstInput.InstanceIds, input.InstanceIds...)
@@ -92,7 +96,7 @@ func execDescribeInstanceBatch(ec2api iface.EC2) batcher.BatchExecutor[ec2.Descr
9296
go func(input *ec2.DescribeInstancesInput) {
9397
defer wg.Done()
9498
out, err := ec2api.DescribeInstances(ctx, input)
95-
if err != nil {
99+
if err != nil || len(out) == 0 {
96100
results[idx] = batcher.Result[ec2types.Instance]{Output: nil, Err: err}
97101
return
98102
}
@@ -104,7 +108,11 @@ func execDescribeInstanceBatch(ec2api iface.EC2) batcher.BatchExecutor[ec2.Descr
104108
instanceIDToOutputMap := map[string]ec2types.Instance{}
105109
lo.ForEach(output, func(o ec2types.Instance, _ int) { instanceIDToOutputMap[lo.FromPtr(o.InstanceId)] = o })
106110
for idx, input := range inputs {
107-
o := instanceIDToOutputMap[input.InstanceIds[0]]
111+
o, ok := instanceIDToOutputMap[input.InstanceIds[0]]
112+
if !ok {
113+
results[idx] = batcher.Result[ec2types.Instance]{Output: nil}
114+
continue
115+
}
108116
results[idx] = batcher.Result[ec2types.Instance]{Output: &o}
109117
}
110118
}

pkg/providers/v1/instances_v2_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,142 @@ func TestDescribeInstanceBatching(t *testing.T) {
370370
mockedEC2API.AssertNumberOfCalls(t, "DescribeInstances", 1)
371371
}
372372

373+
// TestDescribeInstanceBatchingWithInstanceDoesntExist will test where one of the instance doesn't exist
374+
func TestDescribeInstanceBatchingWithInstanceDoesntExist(t *testing.T) {
375+
mockedEC2API := newMockedEC2API()
376+
batcher := newdescribeInstanceBatcher(context.Background(), &awsSdkEC2{ec2: mockedEC2API})
377+
378+
mockedEC2API.On("DescribeInstances", mock.Anything).Return(&ec2.DescribeInstancesOutput{
379+
Reservations: []ec2types.Reservation{
380+
{
381+
Instances: []ec2types.Instance{
382+
{
383+
InstanceId: aws.String("Test-1"),
384+
},
385+
{
386+
InstanceId: aws.String("Test-2"),
387+
},
388+
{
389+
InstanceId: aws.String("Test-3"),
390+
},
391+
},
392+
},
393+
},
394+
}, nil)
395+
396+
type result struct {
397+
input string
398+
output []*ec2types.Instance
399+
err error
400+
isInstanceNotFound bool
401+
}
402+
403+
// Add extra space to channel so that we can ensure there were only 3 responses
404+
resCh := make(chan result, 5)
405+
helper := func(wg *sync.WaitGroup, input string, isInstanceNotFound bool) {
406+
defer wg.Done()
407+
res, err := batcher.DescribeInstances(context.Background(), &ec2.DescribeInstancesInput{InstanceIds: []string{input}})
408+
resCh <- result{input: input, output: res, err: err, isInstanceNotFound: isInstanceNotFound}
409+
}
410+
411+
wg := sync.WaitGroup{}
412+
wg.Add(3)
413+
go helper(&wg, "Test-1", false)
414+
go helper(&wg, "Test-2", false)
415+
go helper(&wg, "Test-4", true)
416+
wg.Wait()
417+
close(resCh)
418+
419+
assert.Len(t, resCh, 3)
420+
for res := range resCh {
421+
assert.NoError(t, res.err)
422+
if !res.isInstanceNotFound {
423+
assert.Len(t, res.output, 1)
424+
assert.Equal(t, res.input, *res.output[0].InstanceId)
425+
} else {
426+
assert.Len(t, res.output, 0)
427+
}
428+
}
429+
430+
mockedEC2API.AssertNumberOfCalls(t, "DescribeInstances", 1)
431+
}
432+
433+
// TestDescribeInstanceBatchingWithBatchedRequestFail will test where batched request fails but individual request succeeds
434+
func TestDescribeInstanceBatchingWithBatchedRequestFail(t *testing.T) {
435+
mockedEC2API := newMockedEC2API()
436+
batcher := newdescribeInstanceBatcher(context.Background(), &awsSdkEC2{ec2: mockedEC2API})
437+
var nilResp *ec2.DescribeInstancesOutput
438+
mockedEC2API.On("DescribeInstances", &ec2.DescribeInstancesInput{
439+
InstanceIds: []string{"Test-1", "Test-2"},
440+
}).Return(nilResp, errors.New("instances not found"))
441+
mockedEC2API.On("DescribeInstances", &ec2.DescribeInstancesInput{
442+
InstanceIds: []string{"Test-2", "Test-1"},
443+
}).Return(nilResp, errors.New("instances not found"))
444+
mockedEC2API.On("DescribeInstances",
445+
&ec2.DescribeInstancesInput{
446+
InstanceIds: []string{"Test-1"},
447+
},
448+
).Return(
449+
&ec2.DescribeInstancesOutput{
450+
Reservations: []ec2types.Reservation{
451+
{
452+
Instances: []ec2types.Instance{
453+
{
454+
InstanceId: aws.String("Test-1"),
455+
},
456+
},
457+
},
458+
},
459+
},
460+
nil,
461+
)
462+
mockedEC2API.On("DescribeInstances",
463+
&ec2.DescribeInstancesInput{
464+
InstanceIds: []string{"Test-2"},
465+
},
466+
).Return(
467+
&ec2.DescribeInstancesOutput{
468+
Reservations: []ec2types.Reservation{},
469+
},
470+
nil,
471+
)
472+
473+
type result struct {
474+
input string
475+
output []*ec2types.Instance
476+
err error
477+
isInstanceNotFound bool
478+
}
479+
480+
// Add extra space to channel so that we can ensure there were only 3 responses
481+
resCh := make(chan result, 5)
482+
helper := func(wg *sync.WaitGroup, input string, isInstanceNotFound bool) {
483+
defer wg.Done()
484+
res, err := batcher.DescribeInstances(context.Background(), &ec2.DescribeInstancesInput{InstanceIds: []string{input}})
485+
resCh <- result{input: input, output: res, err: err, isInstanceNotFound: isInstanceNotFound}
486+
}
487+
488+
wg := sync.WaitGroup{}
489+
wg.Add(2)
490+
go helper(&wg, "Test-1", false)
491+
go helper(&wg, "Test-2", true)
492+
wg.Wait()
493+
close(resCh)
494+
495+
assert.Len(t, resCh, 2)
496+
for res := range resCh {
497+
assert.NoError(t, res.err)
498+
if !res.isInstanceNotFound {
499+
assert.Len(t, res.output, 1)
500+
assert.Equal(t, res.input, *res.output[0].InstanceId)
501+
} else {
502+
assert.Len(t, res.output, 0)
503+
}
504+
}
505+
506+
mockedEC2API.AssertNumberOfCalls(t, "DescribeInstances", 3)
507+
}
508+
373509
func getCloudWithMockedDescribeInstances(instanceExists bool, instanceState ec2types.InstanceStateName, instanceID string) *Cloud {
374510
mockedEC2API := newMockedEC2API()
375511
c := &Cloud{ec2: &awsSdkEC2{ec2: mockedEC2API}, describeInstanceBatcher: newdescribeInstanceBatcher(context.Background(), &awsSdkEC2{ec2: mockedEC2API})}

0 commit comments

Comments
 (0)