Provide backpressure to clients when etcd goes down
When etcd is down today we don't specifically handle the error involved, which means clients get a generic 500 error. This commit adds a formal error type internally for both WatchExpired and EtcdUnreachable, and then converts them to api/errors before returning to the client. It also upgrades the client to retry on any 429 or 5xx error that has a Retry-After header, instead of just 429. In combination, this allows the apiserver to exert backpressure on controllers that are hotlooping. Picked 2 seconds by default, but we could potentially ramp that up even further in a future iteration.
This commit is contained in:
@@ -839,7 +839,10 @@ func isTextResponse(resp *http.Response) bool {
|
||||
// checkWait returns true along with a number of seconds if the server instructed us to wait
|
||||
// before retrying.
|
||||
func checkWait(resp *http.Response) (int, bool) {
|
||||
if resp.StatusCode != errors.StatusTooManyRequests {
|
||||
switch r := resp.StatusCode; {
|
||||
// any 500 error code and 429 can trigger a wait
|
||||
case r == errors.StatusTooManyRequests, r >= 500:
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
i, ok := retryAfterSeconds(resp)
|
||||
|
@@ -745,6 +745,38 @@ func TestCheckRetryClosesBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRetryHandles429And5xx(t *testing.T) {
|
||||
count := 0
|
||||
ch := make(chan struct{})
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
t.Logf("attempt %d", count)
|
||||
if count >= 4 {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
close(ch)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Retry-After", "0")
|
||||
w.WriteHeader([]int{apierrors.StatusTooManyRequests, 500, 501, 504}[count])
|
||||
count++
|
||||
}))
|
||||
defer testServer.Close()
|
||||
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: testapi.Default.Version(), Username: "user", Password: "pass"})
|
||||
_, err := c.Verb("POST").
|
||||
Prefix("foo", "bar").
|
||||
Suffix("baz").
|
||||
Timeout(time.Second).
|
||||
Body([]byte(strings.Repeat("abcd", 1000))).
|
||||
DoRaw()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v %#v", err, err)
|
||||
}
|
||||
<-ch
|
||||
if count != 4 {
|
||||
t.Errorf("unexpected retries: %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCheckRetryClosesBody(t *testing.B) {
|
||||
count := 0
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -771,6 +803,7 @@ func BenchmarkCheckRetryClosesBody(t *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestNewWayReader(t *testing.T) {
|
||||
reqObj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}
|
||||
reqBodyExpected, _ := testapi.Default.Codec().Encode(reqObj)
|
||||
|
Reference in New Issue
Block a user