Merge pull request #89566 from yue9944882/feat/override-clientside-retry

Opt-out/Override client-side max-retry
This commit is contained in:
Kubernetes Prow Robot 2020-04-02 12:42:38 -07:00 committed by GitHub
commit 691fa9f5a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 3 deletions

View File

@ -91,6 +91,7 @@ type Request struct {
rateLimiter flowcontrol.RateLimiter rateLimiter flowcontrol.RateLimiter
backoff BackoffManager backoff BackoffManager
timeout time.Duration timeout time.Duration
maxRetries int
// generic components accessible via method setters // generic components accessible via method setters
verb string verb string
@ -139,6 +140,7 @@ func NewRequest(c *RESTClient) *Request {
backoff: backoff, backoff: backoff,
timeout: timeout, timeout: timeout,
pathPrefix: pathPrefix, pathPrefix: pathPrefix,
maxRetries: 10,
} }
switch { switch {
@ -391,6 +393,18 @@ func (r *Request) Timeout(d time.Duration) *Request {
return r return r
} }
// MaxRetries makes the request use the given integer as a ceiling of retrying upon receiving
// "Retry-After" headers and 429 status-code in the response. The default is 10 unless this
// function is specifically called with a different value.
// A zero maxRetries prevent it from doing retires and return an error immediately.
func (r *Request) MaxRetries(maxRetries int) *Request {
if maxRetries < 0 {
maxRetries = 0
}
r.maxRetries = maxRetries
return r
}
// Body makes the request use obj as the body. Optional. // Body makes the request use obj as the body. Optional.
// If obj is a string, try to read a file of that name. // If obj is a string, try to read a file of that name.
// If obj is a []byte, send it directly. // If obj is a []byte, send it directly.
@ -831,7 +845,6 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
} }
// Right now we make about ten retry attempts if we get a Retry-After response. // Right now we make about ten retry attempts if we get a Retry-After response.
maxRetries := 10
retries := 0 retries := 0
for { for {
@ -894,7 +907,7 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
}() }()
retries++ retries++
if seconds, wait := checkWait(resp); wait && retries < maxRetries { if seconds, wait := checkWait(resp); wait && retries <= r.maxRetries {
if seeker, ok := r.body.(io.Seeker); ok && r.body != nil { if seeker, ok := r.body.(io.Seeker); ok && r.body != nil {
_, err := seeker.Seek(0, 0) _, err := seeker.Seek(0, 0)
if err != nil { if err != nil {

View File

@ -1403,7 +1403,8 @@ func TestConnectionResetByPeerIsRetried(t *testing.T) {
return nil, &net.OpError{Err: syscall.ECONNRESET} return nil, &net.OpError{Err: syscall.ECONNRESET}
}), }),
}, },
backoff: backoff, backoff: backoff,
maxRetries: 10,
} }
// We expect two retries of "connection reset by peer" and the success. // We expect two retries of "connection reset by peer" and the success.
_, err := req.Do(context.Background()).Raw() _, err := req.Do(context.Background()).Raw()
@ -2218,3 +2219,62 @@ func TestThrottledLogger(t *testing.T) {
t.Fatalf("expected %v log messages, but got %v", e, a) t.Fatalf("expected %v log messages, but got %v", e, a)
} }
} }
func TestRequestMaxRetries(t *testing.T) {
successAtNthCalls := 1
actualCalls := 0
retryOneTimeHandler := func(w http.ResponseWriter, req *http.Request) {
defer func() { actualCalls++ }()
if actualCalls >= successAtNthCalls {
w.WriteHeader(http.StatusOK)
return
}
w.Header().Set("Retry-After", "1")
w.WriteHeader(http.StatusTooManyRequests)
actualCalls++
}
testServer := httptest.NewServer(http.HandlerFunc(retryOneTimeHandler))
defer testServer.Close()
u, err := url.Parse(testServer.URL)
if err != nil {
t.Error(err)
}
testCases := []struct {
name string
maxRetries int
expectError bool
}{
{
name: "no retrying should fail",
maxRetries: 0,
expectError: true,
},
{
name: "1 max-retry should exactly work",
maxRetries: 1,
expectError: false,
},
{
name: "5 max-retry should work",
maxRetries: 5,
expectError: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
defer func() { actualCalls = 0 }()
_, err := NewRequestWithClient(u, "", defaultContentConfig(), testServer.Client()).
Verb("get").
MaxRetries(testCase.maxRetries).
AbsPath("/foo").
DoRaw(context.TODO())
hasError := err != nil
if testCase.expectError != hasError {
t.Error(" failed checking error")
}
})
}
}