From e3beb648486913f7a9266485ddb9523acbd4cc1f Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 21 Dec 2022 17:50:36 +0100 Subject: [PATCH] add retry to new 'do' functionality (#64) Co-authored-by: Dean Oren --- client.go | 38 +++++++++++++++++++++++++++++++++----- client_test.go | 49 +++++++++++++++++++++++++++++++++++++++++-------- go.mod | 6 +++++- go.sum | 15 ++++++++++++--- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index e269e93c..f1858315 100644 --- a/client.go +++ b/client.go @@ -18,6 +18,7 @@ import ( "github.com/SchwarzIT/community-stackit-go-client/internal/common" "github.com/SchwarzIT/community-stackit-go-client/pkg/validate" "golang.org/x/oauth2" + waitutil "k8s.io/apimachinery/pkg/util/wait" ) // Client service for managing interactions with STACKIT API @@ -26,6 +27,11 @@ type Client struct { client *http.Client config Config + // when an internal server error is encountered + // the call will be retried + RetryTimout time.Duration // timeout for retrying a call + RetryWait time.Duration // how long to wait before trying again + Services services // Legacy @@ -45,8 +51,10 @@ func New(ctx context.Context, cfg Config) (*Client, error) { } c := &Client{ - config: cfg, - ctx: ctx, + config: cfg, + ctx: ctx, + RetryTimout: 2 * time.Minute, + RetryWait: 30 * time.Second, } c.setHttpClient(c.ctx) @@ -121,15 +129,15 @@ func (c *Client) Request(ctx context.Context, method, path string, body []byte) // LegacyDo performs the request, including retry if set // To set retry, use WithRetry() which returns a shalow copy of the client func (c *Client) LegacyDo(req *http.Request, v interface{}, errorHandlers ...func(*http.Response) error) (*http.Response, error) { - return c.do(req, v, errorHandlers...) + return c.legacyDo(req, v, errorHandlers...) } func (c *Client) Do(req *http.Request) (*http.Response, error) { - return c.LegacyDo(req, nil) + return c.do(req) } // Do performs the request and decodes the response if given interface != nil -func (c *Client) do(req *http.Request, v interface{}, errorHandlers ...func(*http.Response) error) (*http.Response, error) { +func (c *Client) legacyDo(req *http.Request, v interface{}, errorHandlers ...func(*http.Response) error) (*http.Response, error) { resp, err := c.client.Do(req) if err != nil { return nil, err @@ -153,6 +161,26 @@ func (c *Client) do(req *http.Request, v interface{}, errorHandlers ...func(*htt return resp, err } +// Do performs the request and decodes the response if given interface != nil +func (c *Client) do(req *http.Request) (resp *http.Response, err error) { + if err := waitutil.PollImmediate(c.RetryWait, c.RetryTimout, waitutil.ConditionFunc( + func() (bool, error) { + resp, err = c.client.Do(req) + if err != nil { + return false, err + } + if resp != nil && resp.StatusCode == http.StatusInternalServerError { + return false, nil + } + return true, nil + }), + ); err != nil { + return resp, err + } + + return resp, err +} + // MockServer mocks STACKIT api server // and returns a client pointing to it, mux, teardown function and an error indicator func MockServer() (c *Client, mux *http.ServeMux, teardown func(), err error) { diff --git a/client_test.go b/client_test.go index d191e216..6e31114a 100644 --- a/client_test.go +++ b/client_test.go @@ -9,6 +9,7 @@ import ( "net/url" "reflect" "testing" + "time" "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" ) @@ -244,21 +245,21 @@ func TestClient_SetToken(t *testing.T) { } } -func TestClient_DoWithRetryNonRetryableErrorAndTestBaseURLChange(t *testing.T) { - c, mux, teardown, err := MockServer() +func TestClient_GeneralTests(t *testing.T) { + c, _, teardown, err := MockServer() defer teardown() if err != nil { t.Errorf("error from mock.AuthServer: %s", err.Error()) } - mux.HandleFunc("/err", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - }) + basetime := 200 * time.Millisecond + c.RetryTimout = 5 * basetime + c.RetryWait = basetime - req, _ := c.Request(context.Background(), http.MethodGet, "/err", nil) + c.SetBaseURL("ht#@://aa") + req, _ := c.Request(context.Background(), http.MethodGet, "err2", nil) if _, err := c.Do(req); err == nil { - t.Error("expected do request to return error but got nil instead") + t.Error("expected do request to return error") } c.SetBaseURL(consts.DEFAULT_BASE_URL) @@ -270,3 +271,35 @@ func TestClient_DoWithRetryNonRetryableErrorAndTestBaseURLChange(t *testing.T) { t.Errorf("expected base URL to be %s, got %s instead", consts.DEFAULT_BASE_URL, cfg.BaseUrl.String()) } } + +func TestClient_DoWithRetry(t *testing.T) { + c, mux, teardown, err := MockServer() + defer teardown() + if err != nil { + t.Errorf("error from mock.AuthServer: %s", err.Error()) + } + + basetime := 200 * time.Millisecond + ctx := context.Background() + ctx1, td1 := context.WithTimeout(ctx, 2*basetime) + defer td1() + + c.RetryTimout = 5 * basetime + c.RetryWait = basetime + + mux.HandleFunc("/ep", func(w http.ResponseWriter, r *http.Request) { + if ctx1.Err() == nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("{}")) + }) + + req, _ := c.Request(context.Background(), http.MethodGet, "/ep", nil) + if _, err := c.Do(req); err != nil { + t.Error(err) + } +} diff --git a/go.mod b/go.mod index ffdf3397..d30a5d67 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,16 @@ require ( github.com/pkg/errors v0.9.1 golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b golang.org/x/oauth2 v0.3.0 + k8s.io/apimachinery v0.26.0 ) require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect github.com/golang/protobuf v1.5.2 // indirect - golang.org/x/net v0.3.0 // indirect + golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect ) diff --git a/go.sum b/go.sum index 85af12d7..fd8e1e91 100644 --- a/go.sum +++ b/go.sum @@ -6,12 +6,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/do87/oapi-codegen v0.0.1 h1:Y60e0PhwIRcDafJE+W9U56dDX+U+AE6BL7P35ZWma4w= github.com/do87/oapi-codegen v0.0.1/go.mod h1:TNxhKQvKeywM5hQPdfhD6B5O7t5ePBNGajSkA+NZv8c= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= @@ -29,8 +32,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4= golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -45,3 +48,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=