From 28968144d6188e0bc66f8b8c8b22329bb3996228 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Fri, 20 Nov 2015 13:48:12 +0900 Subject: [PATCH] update aws-sdk-go v1.0.0 --- Godeps/Godeps.json | 40 +-- .../aws/aws-sdk-go/aws/awsutil/path_value.go | 26 +- .../aws-sdk-go/aws/awsutil/path_value_test.go | 34 +++ .../aws/aws-sdk-go/aws/client/client.go | 17 +- .../github.com/aws/aws-sdk-go/aws/config.go | 22 ++ .../aws/request/request_pagination.go | 14 +- .../aws/request/request_pagination_test.go | 63 +++++ .../aws/aws-sdk-go/aws/request/retryer.go | 8 + .../github.com/aws/aws-sdk-go/aws/version.go | 2 +- .../aws/aws-sdk-go/private/waiter/waiter.go | 95 ++++--- .../aws-sdk-go/private/waiter/waiter_test.go | 252 ++++++++++++++++-- 11 files changed, 485 insertions(+), 88 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ee9d78e1470..73c38fffec0 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -20,53 +20,53 @@ }, { "ImportPath": "github.com/aws/aws-sdk-go/aws", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/endpoints", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/waiter", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatch", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/ec2", - "Comment": "v0.10.4-18-gce51895", - "Rev": "ce51895e994693d65ab997ae48032bf13a9290b7" + "Comment": "v1.0.0", + "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, { "ImportPath": "github.com/davecgh/go-spew/spew", diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go index 8c0dc2d8d24..4d2a01e8c41 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go @@ -13,11 +13,11 @@ var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`) // rValuesAtPath returns a slice of values found in value v. The values // in v are explored recursively so all nested values are collected. -func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) []reflect.Value { +func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTerm bool) []reflect.Value { pathparts := strings.Split(path, "||") if len(pathparts) > 1 { for _, pathpart := range pathparts { - vals := rValuesAtPath(v, pathpart, create, caseSensitive) + vals := rValuesAtPath(v, pathpart, createPath, caseSensitive, nilTerm) if len(vals) > 0 { return vals } @@ -76,7 +76,16 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) return false }) - if create && value.Kind() == reflect.Ptr && value.IsNil() { + if nilTerm && value.Kind() == reflect.Ptr && len(components[1:]) == 0 { + if !value.IsNil() { + value.Set(reflect.Zero(value.Type())) + } + return []reflect.Value{value} + } + + if createPath && value.Kind() == reflect.Ptr && value.IsNil() { + // TODO if the value is the terminus it should not be created + // if the value to be set to its position is nil. value.Set(reflect.New(value.Type().Elem())) value = value.Elem() } else { @@ -84,7 +93,7 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) } if value.Kind() == reflect.Slice || value.Kind() == reflect.Map { - if !create && value.IsNil() { + if !createPath && value.IsNil() { value = reflect.ValueOf(nil) } } @@ -116,7 +125,7 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) // pull out index i := int(*index) if i >= value.Len() { // check out of bounds - if create { + if createPath { // TODO resize slice } else { continue @@ -127,7 +136,7 @@ func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) value = reflect.Indirect(value.Index(i)) if value.Kind() == reflect.Slice || value.Kind() == reflect.Map { - if !create && value.IsNil() { + if !createPath && value.IsNil() { value = reflect.ValueOf(nil) } } @@ -176,8 +185,11 @@ func ValuesAtPath(i interface{}, path string) ([]interface{}, error) { // SetValueAtPath sets a value at the case insensitive lexical path inside // of a structure. func SetValueAtPath(i interface{}, path string, v interface{}) { - if rvals := rValuesAtPath(i, path, true, false); rvals != nil { + if rvals := rValuesAtPath(i, path, true, false, v == nil); rvals != nil { for _, rval := range rvals { + if rval.Kind() == reflect.Ptr && rval.IsNil() { + continue + } setValue(rval, v) } } diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go index 378a4864848..b2225566fa3 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go @@ -105,4 +105,38 @@ func TestSetValueAtPathSuccess(t *testing.T) { assert.Equal(t, "test0", s2.B.B.C) awsutil.SetValueAtPath(&s2, "A", []Struct{{}}) assert.Equal(t, []Struct{{}}, s2.A) + + str := "foo" + + s3 := Struct{} + awsutil.SetValueAtPath(&s3, "b.b.c", str) + assert.Equal(t, "foo", s3.B.B.C) + + s3 = Struct{B: &Struct{B: &Struct{C: str}}} + awsutil.SetValueAtPath(&s3, "b.b.c", nil) + assert.Equal(t, "", s3.B.B.C) + + s3 = Struct{} + awsutil.SetValueAtPath(&s3, "b.b.c", nil) + assert.Equal(t, "", s3.B.B.C) + + s3 = Struct{} + awsutil.SetValueAtPath(&s3, "b.b.c", &str) + assert.Equal(t, "foo", s3.B.B.C) + + var s4 struct{ Name *string } + awsutil.SetValueAtPath(&s4, "Name", str) + assert.Equal(t, str, *s4.Name) + + s4 = struct{ Name *string }{} + awsutil.SetValueAtPath(&s4, "Name", nil) + assert.Equal(t, (*string)(nil), s4.Name) + + s4 = struct{ Name *string }{Name: &str} + awsutil.SetValueAtPath(&s4, "Name", nil) + assert.Equal(t, (*string)(nil), s4.Name) + + s4 = struct{ Name *string }{} + awsutil.SetValueAtPath(&s4, "Name", &str) + assert.Equal(t, str, *s4.Name) } diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/client/client.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/client/client.go index 63ab805b9c4..e639ce040d7 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/client/client.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/client/client.go @@ -41,11 +41,20 @@ func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, op Handlers: handlers, } - maxRetries := aws.IntValue(cfg.MaxRetries) - if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries { - maxRetries = 3 + switch retryer, ok := cfg.Retryer.(request.Retryer); { + case ok: + svc.Retryer = retryer + case cfg.Retryer != nil && cfg.Logger != nil: + s := fmt.Sprintf("WARNING: %T does not implement request.Retryer; using DefaultRetryer instead", cfg.Retryer) + cfg.Logger.Log(s) + fallthrough + default: + maxRetries := aws.IntValue(cfg.MaxRetries) + if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries { + maxRetries = 3 + } + svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries} } - svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries} svc.AddDebugHandlers() diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go index f157f8b3b5a..75fcc8284b9 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go @@ -12,6 +12,9 @@ import ( // is nil also. const UseServiceDefaultRetries = -1 +// RequestRetryer is an alias for a type that implements the request.Retryer interface. +type RequestRetryer interface{} + // A Config provides service configuration for service clients. By default, // all clients will use the {defaults.DefaultConfig} structure. type Config struct { @@ -59,6 +62,21 @@ type Config struct { // configuration. MaxRetries *int + // Retryer guides how HTTP requests should be retried in case of recoverable failures. + // + // When nil or the value does not implement the request.Retryer interface, + // the request.DefaultRetryer will be used. + // + // When both Retryer and MaxRetries are non-nil, the former is used and + // the latter ignored. + // + // To set the Retryer field in a type-safe manner and with chaining, use + // the request.WithRetryer helper function: + // + // cfg := request.WithRetryer(aws.NewConfig(), myRetryer) + // + Retryer RequestRetryer + // Disables semantic parameter validation, which validates input for missing // required fields and/or other semantic request input errors. DisableParamValidation *bool @@ -217,6 +235,10 @@ func mergeInConfig(dst *Config, other *Config) { dst.MaxRetries = other.MaxRetries } + if other.Retryer != nil { + dst.Retryer = other.Retryer + } + if other.DisableParamValidation != nil { dst.DisableParamValidation = other.DisableParamValidation } diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination.go index b19fbd70b4a..2939ec473f2 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination.go @@ -44,12 +44,19 @@ func (r *Request) nextPageTokens() []interface{} { } tokens := []interface{}{} + tokenAdded := false for _, outToken := range r.Operation.OutputTokens { v, _ := awsutil.ValuesAtPath(r.Data, outToken) if len(v) > 0 { tokens = append(tokens, v[0]) + tokenAdded = true + } else { + tokens = append(tokens, nil) } } + if !tokenAdded { + return nil + } return tokens } @@ -85,9 +92,10 @@ func (r *Request) NextPage() *Request { // return true to keep iterating or false to stop. func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error { for page := r; page != nil; page = page.NextPage() { - page.Send() - shouldContinue := fn(page.Data, !page.HasNextPage()) - if page.Error != nil || !shouldContinue { + if err := page.Send(); err != nil { + return err + } + if getNextPage := fn(page.Data, !page.HasNextPage()); !getNextPage { return page.Error } } diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination_test.go index a9863eaf97f..95a8d99fbe6 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination_test.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/request_pagination_test.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/awstesting/unit" "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/s3" ) @@ -314,7 +315,69 @@ func TestPaginationTruncation(t *testing.T) { assert.Equal(t, []string{"Key1", "Key2"}, results) assert.Nil(t, err) +} +func TestPaginationNilToken(t *testing.T) { + client := route53.New(unit.Session) + + reqNum := 0 + resps := []*route53.ListResourceRecordSetsOutput{ + { + ResourceRecordSets: []*route53.ResourceRecordSet{ + {Name: aws.String("first.example.com.")}, + }, + IsTruncated: aws.Bool(true), + NextRecordName: aws.String("second.example.com."), + NextRecordType: aws.String("MX"), + NextRecordIdentifier: aws.String("second"), + MaxItems: aws.String("1"), + }, + { + ResourceRecordSets: []*route53.ResourceRecordSet{ + {Name: aws.String("second.example.com.")}, + }, + IsTruncated: aws.Bool(true), + NextRecordName: aws.String("third.example.com."), + NextRecordType: aws.String("MX"), + MaxItems: aws.String("1"), + }, + { + ResourceRecordSets: []*route53.ResourceRecordSet{ + {Name: aws.String("third.example.com.")}, + }, + IsTruncated: aws.Bool(false), + MaxItems: aws.String("1"), + }, + } + client.Handlers.Send.Clear() // mock sending + client.Handlers.Unmarshal.Clear() + client.Handlers.UnmarshalMeta.Clear() + client.Handlers.ValidateResponse.Clear() + + idents := []string{} + client.Handlers.Build.PushBack(func(r *request.Request) { + p := r.Params.(*route53.ListResourceRecordSetsInput) + idents = append(idents, aws.StringValue(p.StartRecordIdentifier)) + + }) + client.Handlers.Unmarshal.PushBack(func(r *request.Request) { + r.Data = resps[reqNum] + reqNum++ + }) + + params := &route53.ListResourceRecordSetsInput{ + HostedZoneId: aws.String("id-zone"), + } + + results := []string{} + err := client.ListResourceRecordSetsPages(params, func(p *route53.ListResourceRecordSetsOutput, last bool) bool { + results = append(results, *p.ResourceRecordSets[0].Name) + return true + }) + + assert.NoError(t, err) + assert.Equal(t, []string{"", "second", ""}, idents) + assert.Equal(t, []string{"first.example.com.", "second.example.com.", "third.example.com."}, results) } // Benchmarks diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/retryer.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/retryer.go index b06143b8358..ab6fff5ac84 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/retryer.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request/retryer.go @@ -3,6 +3,7 @@ package request import ( "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" ) @@ -15,6 +16,13 @@ type Retryer interface { MaxRetries() int } +// WithRetryer sets a config Retryer value to the given Config returning it +// for chaining. +func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config { + cfg.Retryer = retryer + return cfg +} + // retryableCodes is a collection of service response codes which are retry-able // without any further action. var retryableCodes = map[string]struct{}{ diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go index f4741adfb44..eec3baa1f05 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go @@ -5,4 +5,4 @@ package aws const SDKName = "aws-sdk-go" // SDKVersion is the version of this SDK -const SDKVersion = "0.10.4" +const SDKVersion = "1.0.0" diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter.go index 335af91dc61..e5fb13666ab 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter.go @@ -5,6 +5,7 @@ import ( "reflect" "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/aws/request" @@ -47,52 +48,74 @@ func (w *Waiter) Wait() error { res := method.Call([]reflect.Value{in}) req := res[0].Interface().(*request.Request) req.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Waiter")) - if err := req.Send(); err != nil { - return err - } + err := req.Send() for _, a := range w.Acceptors { + if err != nil && a.Matcher != "error" { + // Only matcher error is valid if there is a request error + continue + } + result := false + var vals []interface{} switch a.Matcher { - case "pathAll": - if vals, _ := awsutil.ValuesAtPath(req.Data, a.Argument); req.Error == nil && vals != nil { - result = true - for _, val := range vals { - if !awsutil.DeepEqual(val, a.Expected) { - result = false - break - } + case "pathAll", "path": + // Require all matches to be equal for result to match + vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument) + result = true + for _, val := range vals { + if !awsutil.DeepEqual(val, a.Expected) { + result = false + break } } case "pathAny": - if vals, _ := awsutil.ValuesAtPath(req.Data, a.Argument); req.Error == nil && vals != nil { - for _, val := range vals { - if awsutil.DeepEqual(val, a.Expected) { - result = true - break - } + // Only a single match needs to equal for the result to match + vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument) + for _, val := range vals { + if awsutil.DeepEqual(val, a.Expected) { + result = true + break } } case "status": s := a.Expected.(int) result = s == req.HTTPResponse.StatusCode + case "error": + if aerr, ok := err.(awserr.Error); ok { + result = aerr.Code() == a.Expected.(string) + } + case "pathList": + // ignored matcher + default: + logf(client, "WARNING: Waiter for %s encountered unexpected matcher: %s", + w.Config.Operation, a.Matcher) } - if result { - switch a.State { - case "success": - return nil // waiter completed - case "failure": - if req.Error == nil { - return awserr.New("ResourceNotReady", - fmt.Sprintf("failed waiting for successful resource state"), nil) - } - return req.Error // waiter failed - case "retry": - // do nothing, just retry - } - break + if !result { + // If there was no matching result found there is nothing more to do + // for this response, retry the request. + continue } + + switch a.State { + case "success": + // waiter completed + return nil + case "failure": + // Waiter failure state triggered + return awserr.New("ResourceNotReady", + fmt.Sprintf("failed waiting for successful resource state"), err) + case "retry": + // clear the error and retry the operation + err = nil + default: + logf(client, "WARNING: Waiter for %s encountered unexpected state: %s", + w.Config.Operation, a.State) + } + } + if err != nil { + return err } time.Sleep(time.Second * time.Duration(w.Delay)) @@ -101,3 +124,13 @@ func (w *Waiter) Wait() error { return awserr.New("ResourceNotReady", fmt.Sprintf("exceeded %d wait attempts", w.MaxAttempts), nil) } + +func logf(client reflect.Value, msg string, args ...interface{}) { + cfgVal := client.FieldByName("Config") + if !cfgVal.IsValid() { + return + } + if cfg, ok := cfgVal.Interface().(*aws.Config); ok && cfg.Logger != nil { + cfg.Logger.Log(fmt.Sprintf(msg, args...)) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter_test.go index 58267295b11..d545f35370f 100644 --- a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter_test.go +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/private/waiter/waiter_test.go @@ -1,6 +1,9 @@ package waiter_test import ( + "bytes" + "io/ioutil" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -41,22 +44,7 @@ func (c *mockClient) MockRequest(input *MockInput) (*request.Request, *MockOutpu return req, output } -var mockAcceptors = []waiter.WaitAcceptor{ - { - State: "success", - Matcher: "pathAll", - Argument: "States[].State", - Expected: "running", - }, - { - State: "failure", - Matcher: "pathAny", - Argument: "States[].State", - Expected: "stopping", - }, -} - -func TestWaiter(t *testing.T) { +func TestWaiterPathAll(t *testing.T) { svc := &mockClient{Client: awstesting.NewClient(&aws.Config{ Region: aws.String("mock-region"), })} @@ -73,13 +61,13 @@ func TestWaiter(t *testing.T) { {State: aws.String("pending")}, }, }, - { // Request 1 + { // Request 2 States: []*MockState{ {State: aws.String("running")}, {State: aws.String("pending")}, }, }, - { // Request 1 + { // Request 3 States: []*MockState{ {State: aws.String("running")}, {State: aws.String("running")}, @@ -104,7 +92,83 @@ func TestWaiter(t *testing.T) { Operation: "Mock", Delay: 0, MaxAttempts: 10, - Acceptors: mockAcceptors, + Acceptors: []waiter.WaitAcceptor{ + { + State: "success", + Matcher: "pathAll", + Argument: "States[].State", + Expected: "running", + }, + }, + } + w := waiter.Waiter{ + Client: svc, + Input: &MockInput{}, + Config: waiterCfg, + } + + err := w.Wait() + assert.NoError(t, err) + assert.Equal(t, 3, numBuiltReq) + assert.Equal(t, 3, reqNum) +} + +func TestWaiterPath(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.ValidateResponse.Clear() + + reqNum := 0 + resps := []*MockOutput{ + { // Request 1 + States: []*MockState{ + {State: aws.String("pending")}, + {State: aws.String("pending")}, + }, + }, + { // Request 2 + States: []*MockState{ + {State: aws.String("running")}, + {State: aws.String("pending")}, + }, + }, + { // Request 3 + States: []*MockState{ + {State: aws.String("running")}, + {State: aws.String("running")}, + }, + }, + } + + numBuiltReq := 0 + svc.Handlers.Build.PushBack(func(r *request.Request) { + numBuiltReq++ + }) + svc.Handlers.Unmarshal.PushBack(func(r *request.Request) { + if reqNum >= len(resps) { + assert.Fail(t, "too many polling requests made") + return + } + r.Data = resps[reqNum] + reqNum++ + }) + + waiterCfg := waiter.Config{ + Operation: "Mock", + Delay: 0, + MaxAttempts: 10, + Acceptors: []waiter.WaitAcceptor{ + { + State: "success", + Matcher: "path", + Argument: "States[].State", + Expected: "running", + }, + }, } w := waiter.Waiter{ Client: svc, @@ -135,13 +199,13 @@ func TestWaiterFailure(t *testing.T) { {State: aws.String("pending")}, }, }, - { // Request 1 + { // Request 2 States: []*MockState{ {State: aws.String("running")}, {State: aws.String("pending")}, }, }, - { // Request 1 + { // Request 3 States: []*MockState{ {State: aws.String("running")}, {State: aws.String("stopping")}, @@ -166,7 +230,20 @@ func TestWaiterFailure(t *testing.T) { Operation: "Mock", Delay: 0, MaxAttempts: 10, - Acceptors: mockAcceptors, + Acceptors: []waiter.WaitAcceptor{ + { + State: "success", + Matcher: "pathAll", + Argument: "States[].State", + Expected: "running", + }, + { + State: "failure", + Matcher: "pathAny", + Argument: "States[].State", + Expected: "stopping", + }, + }, } w := waiter.Waiter{ Client: svc, @@ -181,3 +258,134 @@ func TestWaiterFailure(t *testing.T) { assert.Equal(t, 3, numBuiltReq) assert.Equal(t, 3, reqNum) } + +func TestWaiterError(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.ValidateResponse.Clear() + + reqNum := 0 + resps := []*MockOutput{ + { // Request 1 + States: []*MockState{ + {State: aws.String("pending")}, + {State: aws.String("pending")}, + }, + }, + { // Request 2, error case + }, + { // Request 3 + States: []*MockState{ + {State: aws.String("running")}, + {State: aws.String("running")}, + }, + }, + } + + numBuiltReq := 0 + svc.Handlers.Build.PushBack(func(r *request.Request) { + numBuiltReq++ + }) + svc.Handlers.Send.PushBack(func(r *request.Request) { + if reqNum == 1 { + r.Error = awserr.New("MockException", "mock exception message", nil) + r.HTTPResponse = &http.Response{ + StatusCode: 400, + Status: http.StatusText(400), + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + } + reqNum++ + } + }) + svc.Handlers.Unmarshal.PushBack(func(r *request.Request) { + if reqNum >= len(resps) { + assert.Fail(t, "too many polling requests made") + return + } + r.Data = resps[reqNum] + reqNum++ + }) + + waiterCfg := waiter.Config{ + Operation: "Mock", + Delay: 0, + MaxAttempts: 10, + Acceptors: []waiter.WaitAcceptor{ + { + State: "success", + Matcher: "pathAll", + Argument: "States[].State", + Expected: "running", + }, + { + State: "retry", + Matcher: "error", + Argument: "", + Expected: "MockException", + }, + }, + } + w := waiter.Waiter{ + Client: svc, + Input: &MockInput{}, + Config: waiterCfg, + } + + err := w.Wait() + assert.NoError(t, err) + assert.Equal(t, 3, numBuiltReq) + assert.Equal(t, 3, reqNum) +} + +func TestWaiterStatus(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.ValidateResponse.Clear() + + reqNum := 0 + svc.Handlers.Build.PushBack(func(r *request.Request) { + reqNum++ + }) + svc.Handlers.Send.PushBack(func(r *request.Request) { + code := 200 + if reqNum == 3 { + code = 404 + } + r.HTTPResponse = &http.Response{ + StatusCode: code, + Status: http.StatusText(code), + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + } + }) + + waiterCfg := waiter.Config{ + Operation: "Mock", + Delay: 0, + MaxAttempts: 10, + Acceptors: []waiter.WaitAcceptor{ + { + State: "success", + Matcher: "status", + Argument: "", + Expected: 404, + }, + }, + } + w := waiter.Waiter{ + Client: svc, + Input: &MockInput{}, + Config: waiterCfg, + } + + err := w.Wait() + assert.NoError(t, err) + assert.Equal(t, 3, reqNum) +}