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

Retry on errors with specified HTTP methods #40

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
39 changes: 32 additions & 7 deletions restapi/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type apiClientOpt struct {
xssi_prefix string
use_cookies bool
debug bool
retry_methods []string
}

type api_client struct {
Expand All @@ -41,7 +42,6 @@ type api_client struct {
username string
password string
headers map[string]string
redirects int
use_cookie bool
timeout int
id_attribute string
Expand All @@ -54,6 +54,7 @@ type api_client struct {
create_returns_object bool
xssi_prefix string
debug bool
retry_methods []string
}

// Make a new api client for RESTful calls
Expand Down Expand Up @@ -122,7 +123,7 @@ func NewAPIClient(opt *apiClientOpt) (*api_client, error) {
create_returns_object: opt.create_returns_object,
xssi_prefix: opt.xssi_prefix,
debug: opt.debug,
redirects: 5,
retry_methods: opt.retry_methods,
}

if opt.debug {
Expand All @@ -149,9 +150,23 @@ func (obj *api_client) toString() string {
for _, n := range obj.copy_keys {
buffer.WriteString(fmt.Sprintf(" %s", n))
}
buffer.WriteString(fmt.Sprintf("retry_methods:\n"))
for _, n := range obj.retry_methods {
buffer.WriteString(fmt.Sprintf(" %s", n))
}
return buffer.String()
}

/* Helper function for retry_methods condition */
func sliceContains(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

/* Helper function that handles sending/receiving and handling
of HTTP data in and out.
TODO: Handle redirects */
Expand Down Expand Up @@ -214,7 +229,13 @@ func (client *api_client) send_request(method string, path string, data string)
log.Printf("%s\n", body)
}

for num_redirects := client.redirects; num_redirects >= 0; num_redirects-- {
/* Retry only if this is one of the user specified HTTP methods to retry on */
num_retries := 0
if len(client.retry_methods) > 0 && sliceContains(method, client.retry_methods) {
num_retries = 5
}

for num_retries >= 0 {
resp, err := client.http_client.Do(req)

if err != nil {
Expand All @@ -241,8 +262,12 @@ func (client *api_client) send_request(method string, path string, data string)
body := strings.TrimPrefix(string(bodyBytes), client.xssi_prefix)

if resp.StatusCode == 301 || resp.StatusCode == 302 {
//Redirecting... decrement num_redirects and proceed to the next loop
//Redirecting... decrement num_retries and proceed to the next loop
//uri = URI.parse(rsp['Location'])
} else if num_retries != 0 && resp.StatusCode >= 500 && resp.StatusCode < 600 {
if client.debug {
log.Printf("Received response code '%d': %s - Retrying", resp.StatusCode, body)
}
} else if resp.StatusCode == 404 || resp.StatusCode < 200 || resp.StatusCode >= 303 {
return "", errors.New(fmt.Sprintf("Unexpected response code '%d': %s", resp.StatusCode, body))
} else {
Expand All @@ -251,8 +276,8 @@ func (client *api_client) send_request(method string, path string, data string)
}
return body, nil
}
num_retries--
} //End loop through retry attempts

} //End loop through redirect attempts

return "", errors.New("Error - too many redirects!")
return "", errors.New("Error - too many retries!")
}
22 changes: 20 additions & 2 deletions restapi/api_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func TestAPIClient(t *testing.T) {
copy_keys: make([]string, 0),
write_returns_object: false,
create_returns_object: false,
debug: debug,
debug: debug,
retry_methods: []string{"GET"},
}
client, _ := NewAPIClient(opt)

Expand Down Expand Up @@ -67,6 +68,18 @@ func TestAPIClient(t *testing.T) {
t.Fatalf("client_test.go: Timeout did not trigger on slow request")
}

/* Verify retry on 500 error works */
if debug {
log.Printf("api_client_test.go: Testing retry on 500 errors\n")
}
res, err = client.send_request("GET", "/error", "")
if err != nil {
t.Fatalf("client_test.go: %s", err)
}
if res != "The 2nd try will work" {
t.Fatalf("client_test.go: Got back '%s' but expected 'The 2nd try will work'\n", res)
}

if debug {
log.Println("client_test.go: Stopping HTTP server")
}
Expand All @@ -88,7 +101,12 @@ func setup_api_client_server() {
serverMux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/ok", http.StatusPermanentRedirect)
})

error := http.StatusInternalServerError
serverMux.HandleFunc("/error", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(error)
w.Write([]byte("The 2nd try will work"))
error = http.StatusOK
})
api_client_server = &http.Server{
Addr: "127.0.0.1:8080",
Handler: serverMux,
Expand Down
14 changes: 14 additions & 0 deletions restapi/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ func Provider() terraform.ResourceProvider {
Description: "Defaults to `DELETE`. The HTTP method used to DELETE objects of this type on the API server.",
Optional: true,
},
"retry_methods": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Description: "Defines a list of HTTP methods to retry on when receiving a HTTP response code in the 500 range. Defaults to an empty list (no retries)",
},
"copy_keys": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Expand Down Expand Up @@ -139,6 +145,13 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) {
}
}

retry_methods := make([]string, 0)
if i_retry_methods := d.Get("retry_methods"); i_retry_methods != nil {
for _, v := range i_retry_methods.([]interface{}) {
retry_methods = append(retry_methods, v.(string))
}
}

opt := &apiClientOpt{
uri: d.Get("uri").(string),
insecure: d.Get("insecure").(bool),
Expand All @@ -153,6 +166,7 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) {
create_returns_object: d.Get("create_returns_object").(bool),
xssi_prefix: d.Get("xssi_prefix").(string),
debug: d.Get("debug").(bool),
retry_methods: retry_methods,
}

if v, ok := d.GetOk("create_method"); ok {
Expand Down