Skip to content

Commit

Permalink
feat: add retry mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
Meallia committed Dec 6, 2022
1 parent cede381 commit 937a2db
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ description: |-
- **read_method** (String, Optional) Defaults to `GET`. The HTTP method used to READ objects of this type on the API server.
- **test_path** (String, Optional) If set, the provider will issue a read_method request to this path after instantiation requiring a 200 OK response before proceeding. This is useful if your API provides a no-op endpoint that can signal if this provider is configured correctly. Response data will be ignored.
- **timeout** (Number, Optional) When set, will cause requests taking longer than this time (in seconds) to be aborted.
- **retry** (Number, Optional) When set, will cause requests to be retried on network error.
- **update_method** (String, Optional) Defaults to `PUT`. The HTTP method used to UPDATE objects of this type on the API server.
- **use_cookies** (Boolean, Optional) Enable cookie jar to persist session.
- **username** (String, Optional) When set, will use this username for BASIC auth to the API.
Expand Down
43 changes: 42 additions & 1 deletion restapi/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -12,6 +13,7 @@ import (
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strings"
"time"

Expand All @@ -26,6 +28,7 @@ type apiClientOpt struct {
password string
headers map[string]string
timeout int
retry int
idAttribute string
createMethod string
readMethod string
Expand Down Expand Up @@ -75,8 +78,13 @@ type APIClient struct {
rateLimiter *rate.Limiter
debug bool
oauthConfig *clientcredentials.Config
retry int
}

var redirectsErrorRe = regexp.MustCompile(`stopped after \d+ redirects\z`)
var schemeErrorRe = regexp.MustCompile(`unsupported protocol scheme`)
var notTrustedErrorRe = regexp.MustCompile(`certificate is not trusted`)

//NewAPIClient makes a new api client for RESTful calls
func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
if opt.debug {
Expand Down Expand Up @@ -173,6 +181,7 @@ func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
createReturnsObject: opt.createReturnsObject,
xssiPrefix: opt.xssiPrefix,
debug: opt.debug,
retry: opt.retry,
}

if opt.oauthClientID != "" && opt.oauthClientSecret != "" && opt.oauthTokenURL != "" {
Expand Down Expand Up @@ -218,6 +227,8 @@ func (client *APIClient) sendRequest(method string, path string, data string) (s
fullURI := client.uri + path
var req *http.Request
var err error
var resp http.Response


if client.debug {
log.Printf("api_client.go: method='%s', path='%s', full uri (derived)='%s', data='%s'\n", method, path, fullURI, data)
Expand Down Expand Up @@ -290,7 +301,37 @@ func (client *APIClient) sendRequest(method string, path string, data string) (s
_ = client.rateLimiter.Wait(context.Background())
}

resp, err := client.httpClient.Do(req)
for attempt := 0; attempt < client.retry; attempt++ {
resp, err := client.httpClient.Do(req)
if err != nil { // No HTTP response, transport error
if v, ok := err.(*url.Error); ok {
if redirectsErrorRe.MatchString(v.Error()) {
break
}
if schemeErrorRe.MatchString(v.Error()) {
break
}
if notTrustedErrorRe.MatchString(v.Error()) {
break
}
if _, ok := v.Err.(x509.UnknownAuthorityError); ok {
break
}
}
// if none of the above conditions matched, the error is likely recoverable.
// continue
} else { // HTTP response available
if resp.StatusCode == http.StatusTooManyRequests {
continue
}
if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) {
continue
}
// if none of the above conditions matched, the error is likely unrecoverable.
// break
}
time.Sleep(5)
}

if err != nil {
//log.Printf("api_client.go: Error detected: %s\n", err)
Expand Down
1 change: 1 addition & 0 deletions restapi/api_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestAPIClient(t *testing.T) {
password: "",
headers: make(map[string]string),
timeout: 2,
retry: 0,
idAttribute: "id",
copyKeys: make([]string, 0),
writeReturnsObject: false,
Expand Down
1 change: 1 addition & 0 deletions restapi/api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ var client, err = NewAPIClient(&apiClientOpt{
password: "",
headers: make(map[string]string),
timeout: 5,
retry: 0,
idAttribute: "Id",
copyKeys: []string{"Thing"},
writeReturnsObject: true,
Expand Down
1 change: 1 addition & 0 deletions restapi/datasource_api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func TestAccRestapiobject_Basic(t *testing.T) {
password: "",
headers: make(map[string]string),
timeout: 2,
retry: 0,
idAttribute: "id",
copyKeys: make([]string, 0),
writeReturnsObject: false,
Expand Down
1 change: 1 addition & 0 deletions restapi/import_api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func TestAccRestApiObject_importBasic(t *testing.T) {
password: "",
headers: make(map[string]string),
timeout: 2,
retry: 0,
idAttribute: "id",
copyKeys: make([]string, 0),
writeReturnsObject: false,
Expand Down
7 changes: 7 additions & 0 deletions restapi/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ func Provider() *schema.Provider {
DefaultFunc: schema.EnvDefaultFunc("REST_API_TIMEOUT", 0),
Description: "When set, will cause requests taking longer than this time (in seconds) to be aborted.",
},
"retry": {
Type: schema.TypeInt,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("REST_API_RETRY", 0),
Description: "When set, will cause requests to be retried on network error.",
},
"id_attribute": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -232,6 +238,7 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) {
headers: headers,
useCookies: d.Get("use_cookies").(bool),
timeout: d.Get("timeout").(int),
retry: d.Get("retry").(int),
idAttribute: d.Get("id_attribute").(string),
copyKeys: copyKeys,
writeReturnsObject: d.Get("write_returns_object").(bool),
Expand Down
1 change: 1 addition & 0 deletions restapi/resource_api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func TestAccRestApiObject_Basic(t *testing.T) {
password: "",
headers: make(map[string]string),
timeout: 2,
retry: 0,
idAttribute: "id",
copyKeys: make([]string, 0),
writeReturnsObject: false,
Expand Down

0 comments on commit 937a2db

Please sign in to comment.