Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add missing any/none waiter error matching #5316

Merged
merged 2 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
### SDK Enhancements

### SDK Bugs
* Add missing bool error matching.
* This enables waiters defined to match on presence/absence of errors.
13 changes: 11 additions & 2 deletions aws/request/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,17 @@ func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err erro
s := a.Expected.(int)
result = s == req.HTTPResponse.StatusCode
case ErrorWaiterMatch:
if aerr, ok := err.(awserr.Error); ok {
result = aerr.Code() == a.Expected.(string)
switch ex := a.Expected.(type) {
case string:
if aerr, ok := err.(awserr.Error); ok {
result = aerr.Code() == ex
}
case bool:
if ex {
result = err != nil
} else {
result = err == nil
}
}
default:
waiterLogf(l, "WARNING: Waiter %s encountered unexpected matcher: %s",
Expand Down
211 changes: 211 additions & 0 deletions aws/request/waiter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,217 @@ func TestWaiterError(t *testing.T) {
}
}

func TestWaiterRetryAnyError(t *testing.T) {
svc := &mockClient{Client: awstesting.NewClient(&aws.Config{
Region: aws.String("mock-region"),
})}
svc.Handlers.Send.Clear() // mock sending
svc.Handlers.Unmarshal.Clear()
svc.Handlers.UnmarshalMeta.Clear()
svc.Handlers.UnmarshalError.Clear()
svc.Handlers.ValidateResponse.Clear()

var reqNum int
results := []struct {
Out *MockOutput
Err error
}{
{ // retry
Err: awserr.New(
"MockException1", "mock exception message", nil,
),
},
{ // retry
Err: awserr.New(
"MockException2", "mock exception message", nil,
),
},
{ // success
Out: &MockOutput{
States: []*MockState{
{aws.String("running")},
{aws.String("running")},
},
},
},
{ // shouldn't happen
Out: &MockOutput{
States: []*MockState{
{aws.String("running")},
{aws.String("running")},
},
},
},
}

numBuiltReq := 0
svc.Handlers.Build.PushBack(func(r *request.Request) {
numBuiltReq++
})
svc.Handlers.Send.PushBack(func(r *request.Request) {
code := http.StatusOK
r.HTTPResponse = &http.Response{
StatusCode: code,
Status: http.StatusText(code),
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
}
})
svc.Handlers.Unmarshal.PushBack(func(r *request.Request) {
if reqNum >= len(results) {
t.Errorf("too many polling requests made")
return
}
r.Data = results[reqNum].Out
reqNum++
})
svc.Handlers.UnmarshalMeta.PushBack(func(r *request.Request) {
// If there was an error unmarshal error will be called instead of unmarshal
// need to increment count here also
if err := results[reqNum].Err; err != nil {
r.Error = err
reqNum++
}
})

w := request.Waiter{
MaxAttempts: 10,
Delay: request.ConstantWaiterDelay(0),
SleepWithContext: aws.SleepWithContext,
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "States[].State",
Expected: "running",
},
{
State: request.RetryWaiterState,
Matcher: request.ErrorWaiterMatch,
Argument: "",
Expected: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the actual change on behavior, returning true when there's any error even if we don't have a matcher for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is an explicitly configured matcher saying "retry if there's any error".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, where do we actually verify the new behavior then? We setup matchers, we verify we didn't get any error and that we only tried the expected amount of times, but I'm not sure where on these tests we check for the newly added boolean error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestWaiterRetryAnyError (this one) is configured to say "retry on any error" - so that tests the positive / true case - demonstrated by the first two mocked requests resulting in a retry.

Then TestWaiterSuccessNoError just says "immediately succeed on no error" - the negative / false case - demonstrated by the very first mocked response terminating in success.

},
{
State: request.FailureWaiterState,
Matcher: request.ErrorWaiterMatch,
Argument: "",
Expected: "FailureException",
},
},
NewRequest: BuildNewMockRequest(svc, &MockInput{}),
}

err := w.WaitWithContext(aws.BackgroundContext())
if err != nil {
t.Fatalf("expected no error, but did get one: %v", err)
}
if e, a := 3, numBuiltReq; e != a {
t.Errorf("expect %d built requests got %d", e, a)
}
if e, a := 3, reqNum; e != a {
t.Errorf("expect %d reqNum got %d", e, a)
}
}

func TestWaiterSuccessNoError(t *testing.T) {
svc := &mockClient{Client: awstesting.NewClient(&aws.Config{
Region: aws.String("mock-region"),
})}
svc.Handlers.Send.Clear() // mock sending
svc.Handlers.Unmarshal.Clear()
svc.Handlers.UnmarshalMeta.Clear()
svc.Handlers.UnmarshalError.Clear()
svc.Handlers.ValidateResponse.Clear()

var reqNum int
results := []struct {
Out *MockOutput
Err error
}{
{ // success
Out: &MockOutput{
States: []*MockState{
{aws.String("pending")},
},
},
},
{ // shouldn't happen
Out: &MockOutput{
States: []*MockState{
{aws.String("pending")},
{aws.String("pending")},
},
},
},
}

numBuiltReq := 0
svc.Handlers.Build.PushBack(func(r *request.Request) {
numBuiltReq++
})
svc.Handlers.Send.PushBack(func(r *request.Request) {
code := http.StatusOK
r.HTTPResponse = &http.Response{
StatusCode: code,
Status: http.StatusText(code),
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
}
})
svc.Handlers.Unmarshal.PushBack(func(r *request.Request) {
if reqNum >= len(results) {
t.Errorf("too many polling requests made")
return
}
r.Data = results[reqNum].Out
reqNum++
})
svc.Handlers.UnmarshalMeta.PushBack(func(r *request.Request) {
// If there was an error unmarshal error will be called instead of unmarshal
// need to increment count here also
if err := results[reqNum].Err; err != nil {
r.Error = err
reqNum++
}
})

w := request.Waiter{
MaxAttempts: 10,
Delay: request.ConstantWaiterDelay(0),
SleepWithContext: aws.SleepWithContext,
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "States[].State",
Expected: "running",
},
{
State: request.SuccessWaiterState,
Matcher: request.ErrorWaiterMatch,
Argument: "",
Expected: false,
},
{
State: request.FailureWaiterState,
Matcher: request.ErrorWaiterMatch,
Argument: "",
Expected: "FailureException",
},
},
NewRequest: BuildNewMockRequest(svc, &MockInput{}),
}

err := w.WaitWithContext(aws.BackgroundContext())
if err != nil {
t.Fatalf("expected no error, but did get one")
}
if e, a := 1, numBuiltReq; e != a {
t.Errorf("expect %d built requests got %d", e, a)
}
if e, a := 1, reqNum; e != a {
t.Errorf("expect %d reqNum got %d", e, a)
}
}

func TestWaiterStatus(t *testing.T) {
svc := &mockClient{Client: awstesting.NewClient(&aws.Config{
Region: aws.String("mock-region"),
Expand Down
Loading