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 retry logic to client's Validate method #150

Merged
merged 9 commits into from
Apr 18, 2018

Conversation

GrantSheehan
Copy link
Contributor

@GrantSheehan GrantSheehan commented Apr 7, 2018

In order to get around Datadog's API throwing 503 errors with some regularity, adds retry logic for retryable errors.

Background:
I use the Datadog Terraform provider and it's written in a way that most of the failed requests will be retried without much issue, but refreshing the credentials on the provider itself will run the Validate() method, which is currently not able to be retried. As a result, a respectable percentage of our CI/CD builds end up failing due to a returned 503. This seemed like the most pragmatic way to resolve some of those errors (as Datadog's support has been somewhat less than helpful in resolving the cause of the 503's themselves, cough). If you don't like the way it's implemented here, just let me know what changes you'd like made I'll update it.

Thanks!

@GrantSheehan GrantSheehan force-pushed the auth_retry branch 2 times, most recently from 5132778 to 9fa6542 Compare April 7, 2018 20:10
In order to get around Datadog's API throwing 503 errors with some
regularity, adds retry logic for retryable errors.
Copy link
Collaborator

@ojongerius ojongerius left a comment

Choose a reason for hiding this comment

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

Out of general curiosity, have you looked at https://github.com/hashicorp/go-retryablehttp ? I haven't used it but it looks pretty clean and might fit the use case here.

client.go Outdated

defer resp.Body.Close()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not defer closing resp.Body?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was getting errors in unmarshaling JSON during testing, I think it was because I had it in the wrong scope. I've added it back.

@GrantSheehan
Copy link
Contributor Author

I used the expontential backoff retrier package being used in request.go and mirrored more or less what it was doing.

@ojongerius
Copy link
Collaborator

Fair enough, that is indeed what we used before. Just stumbled upon go-retryablehttp and thought it looked neat.

@GrantSheehan
Copy link
Contributor Author

Yeah, for sure, looks like it could make the code significantly easier to reason about. I can take a crack at migrating to it after this is merged, if you'd like.

@ojongerius
Copy link
Collaborator

ojongerius commented Apr 10, 2018

Ok this is entertaining; I wrote the initial validate() method, and should have used doRequestWithRetries instead of doing the request manually. Thank you for wanting to fix the bug I created! 😊

Would you mind refactoring this to use doRequestWithRetries instead of copying the retry logic, or do you see a reason why that would not be desirable?

Re: using go-retryablehttp it could look cleaner, up to you if you want to take that on. Let's see if we can remove the deduplication first.

@GrantSheehan
Copy link
Contributor Author

GrantSheehan commented Apr 10, 2018

I updated my branch to use the doRequestWithRetries method instead.

@GrantSheehan
Copy link
Contributor Author

@ojongerius Is there any additional testing you'd like me to do here? Am I missing anything?

@ojongerius
Copy link
Collaborator

Thanks @GrantSheehan was out due to illness. I'll have a gander tomorrow morning (NZST, UTC+12)!

@GrantSheehan
Copy link
Contributor Author

Sorry to be a pain, this has been a thorn in our side for a while now. Having finally nailed down a root cause and a fix, I'll admit to being a bit eager to get a fix merged.

Glad you're feeling better!

Copy link
Collaborator

@ojongerius ojongerius left a comment

Choose a reason for hiding this comment

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

Less code, I love it!

client.go Outdated

var resp *http.Response
resp, err = client.HttpClient.Do(req)
resp, err = client.doRequestWithRetries(req, maxTime)
if err != nil {
return false, err
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Still needs a defer to close the response.

}
return false, fmt.Errorf("API error %s: %s", resp.Status, body)
}

body, err := ioutil.ReadAll(resp.Body)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please check the value of err

client.go Outdated
}
return false, fmt.Errorf("API error %s: %s", resp.Status, body)
}

body, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &out)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want extra brownie points, improving my code, this could be something like:

if err = json.Unmarshal(body, &out); err != nil {
    return false, err
}

@ojongerius
Copy link
Collaborator

Almost there! 🥇

client.go Outdated
return false, err
}
return false, fmt.Errorf("API error %s: %s", resp.Status, body)
if body, err := ioutil.ReadAll(resp.Body); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this will work, as we'll need body later on.

Copy link
Contributor Author

@GrantSheehan GrantSheehan Apr 18, 2018

Choose a reason for hiding this comment

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

Forgive my golang ignorance, how does doing it in the if change the logic in a way that would effect the content of body here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To answer my own question, it seems to be the := inline declaration/assignment operator. TIL.

client.go Outdated
body, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &out)
if err != nil {
if err = json.Unmarshal(body, &out); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

client.go Outdated
return false, err
}

defer resp.Body.Close()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please move the defer up to just after checking if doRequestWithRetries return an error, I think below line 82.

Copy link
Collaborator

@ojongerius ojongerius left a comment

Choose a reason for hiding this comment

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

See my last comments, very close!

@GrantSheehan
Copy link
Contributor Author

Yep, no problem. I'm still trying to grok some of golang's idioms/syntax, be as nit-picky as you want to be.

Copy link
Collaborator

@ojongerius ojongerius left a comment

Choose a reason for hiding this comment

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

Just one error check and we should be good!

client.go Outdated
if bodyreader != nil {
req.Header.Add("Content-Type", "application/json")
}
req, err := http.NewRequest("GET", uri, nil)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Still needs checking if we got an error here.

@GrantSheehan btw, you were absolutely right about inline error checking. I stand corrected!

Copy link
Collaborator

@ojongerius ojongerius left a comment

Choose a reason for hiding this comment

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

🎉

@ojongerius
Copy link
Collaborator

ojongerius commented Apr 18, 2018

Nice work @GrantSheehan!

Can you confirm you've verified your changes work for you locally? Is there anything we need to check or can improve before we merge this?

@GrantSheehan
Copy link
Contributor Author

GrantSheehan commented Apr 18, 2018

I've been testing by vendoring and installing my forked repo/branch when compiling the Datadog Terraform provider and have had no issues. If there's anything you'd like me to do in addition to that, let me know.

@ojongerius ojongerius merged commit ea1134d into zorkian:master Apr 18, 2018
@ojongerius
Copy link
Collaborator

ojongerius commented Apr 18, 2018

Thanks @GrantSheehan! I'll cut a release for you so you can pull those changes into the Terraform provider.

@ojongerius
Copy link
Collaborator

I've cut release 2.8.4.

@GrantSheehan
Copy link
Contributor Author

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants