mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 18:00:08 +00:00
Merge pull request #16807 from smarterclayton/server_backpressure_on_etcd_down
Auto commit by PR queue bot
This commit is contained in:
commit
6b7115067d
@ -21,12 +21,27 @@ import (
|
|||||||
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
|
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// InterpretListError converts a generic etcd error on a retrieval
|
||||||
|
// operation into the appropriate API error.
|
||||||
|
func InterpretListError(err error, kind string) error {
|
||||||
|
switch {
|
||||||
|
case etcdstorage.IsEtcdNotFound(err):
|
||||||
|
return errors.NewNotFound(kind, "")
|
||||||
|
case etcdstorage.IsEtcdUnreachable(err):
|
||||||
|
return errors.NewServerTimeout(kind, "list", 2) // TODO: make configurable or handled at a higher level
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// InterpretGetError converts a generic etcd error on a retrieval
|
// InterpretGetError converts a generic etcd error on a retrieval
|
||||||
// operation into the appropriate API error.
|
// operation into the appropriate API error.
|
||||||
func InterpretGetError(err error, kind, name string) error {
|
func InterpretGetError(err error, kind, name string) error {
|
||||||
switch {
|
switch {
|
||||||
case etcdstorage.IsEtcdNotFound(err):
|
case etcdstorage.IsEtcdNotFound(err):
|
||||||
return errors.NewNotFound(kind, name)
|
return errors.NewNotFound(kind, name)
|
||||||
|
case etcdstorage.IsEtcdUnreachable(err):
|
||||||
|
return errors.NewServerTimeout(kind, "get", 2) // TODO: make configurable or handled at a higher level
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -38,6 +53,8 @@ func InterpretCreateError(err error, kind, name string) error {
|
|||||||
switch {
|
switch {
|
||||||
case etcdstorage.IsEtcdNodeExist(err):
|
case etcdstorage.IsEtcdNodeExist(err):
|
||||||
return errors.NewAlreadyExists(kind, name)
|
return errors.NewAlreadyExists(kind, name)
|
||||||
|
case etcdstorage.IsEtcdUnreachable(err):
|
||||||
|
return errors.NewServerTimeout(kind, "create", 2) // TODO: make configurable or handled at a higher level
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -49,6 +66,8 @@ func InterpretUpdateError(err error, kind, name string) error {
|
|||||||
switch {
|
switch {
|
||||||
case etcdstorage.IsEtcdTestFailed(err), etcdstorage.IsEtcdNodeExist(err):
|
case etcdstorage.IsEtcdTestFailed(err), etcdstorage.IsEtcdNodeExist(err):
|
||||||
return errors.NewConflict(kind, name, err)
|
return errors.NewConflict(kind, name, err)
|
||||||
|
case etcdstorage.IsEtcdUnreachable(err):
|
||||||
|
return errors.NewServerTimeout(kind, "update", 2) // TODO: make configurable or handled at a higher level
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -60,6 +79,8 @@ func InterpretDeleteError(err error, kind, name string) error {
|
|||||||
switch {
|
switch {
|
||||||
case etcdstorage.IsEtcdNotFound(err):
|
case etcdstorage.IsEtcdNotFound(err):
|
||||||
return errors.NewNotFound(kind, name)
|
return errors.NewNotFound(kind, name)
|
||||||
|
case etcdstorage.IsEtcdUnreachable(err):
|
||||||
|
return errors.NewServerTimeout(kind, "delete", 2) // TODO: make configurable or handled at a higher level
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -215,6 +215,12 @@ const (
|
|||||||
// Status code 500
|
// Status code 500
|
||||||
StatusReasonInternalError = "InternalError"
|
StatusReasonInternalError = "InternalError"
|
||||||
|
|
||||||
|
// StatusReasonExpired indicates that the request is invalid because the content you are requesting
|
||||||
|
// has expired and is no longer available. It is typically associated with watches that can't be
|
||||||
|
// serviced.
|
||||||
|
// Status code 410 (gone)
|
||||||
|
StatusReasonExpired = "Expired"
|
||||||
|
|
||||||
// StatusReasonServiceUnavailable means that the request itself was valid,
|
// StatusReasonServiceUnavailable means that the request itself was valid,
|
||||||
// but the requested service is unavailable at this time.
|
// but the requested service is unavailable at this time.
|
||||||
// Retrying the request after some time might succeed.
|
// Retrying the request after some time might succeed.
|
||||||
|
@ -858,7 +858,10 @@ func isTextResponse(resp *http.Response) bool {
|
|||||||
// checkWait returns true along with a number of seconds if the server instructed us to wait
|
// checkWait returns true along with a number of seconds if the server instructed us to wait
|
||||||
// before retrying.
|
// before retrying.
|
||||||
func checkWait(resp *http.Response) (int, bool) {
|
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
|
return 0, false
|
||||||
}
|
}
|
||||||
i, ok := retryAfterSeconds(resp)
|
i, ok := retryAfterSeconds(resp)
|
||||||
|
@ -764,6 +764,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) {
|
func BenchmarkCheckRetryClosesBody(t *testing.B) {
|
||||||
count := 0
|
count := 0
|
||||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
@ -790,6 +822,7 @@ func BenchmarkCheckRetryClosesBody(t *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDoRequestNewWayReader(t *testing.T) {
|
func TestDoRequestNewWayReader(t *testing.T) {
|
||||||
reqObj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}
|
reqObj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}
|
||||||
reqBodyExpected, _ := testapi.Default.Codec().Encode(reqObj)
|
reqBodyExpected, _ := testapi.Default.Codec().Encode(reqObj)
|
||||||
|
@ -184,10 +184,7 @@ func (e *Etcd) ListPredicate(ctx api.Context, m generic.Matcher, options *api.Li
|
|||||||
trace.Step("About to read single object")
|
trace.Step("About to read single object")
|
||||||
err := e.Storage.GetToList(ctx, key, filterFunc, list)
|
err := e.Storage.GetToList(ctx, key, filterFunc, list)
|
||||||
trace.Step("Object extracted")
|
trace.Step("Object extracted")
|
||||||
if err != nil {
|
return list, etcderr.InterpretListError(err, e.EndpointName)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
}
|
||||||
// if we cannot extract a key based on the current context, the optimization is skipped
|
// if we cannot extract a key based on the current context, the optimization is skipped
|
||||||
}
|
}
|
||||||
@ -202,10 +199,7 @@ func (e *Etcd) ListPredicate(ctx api.Context, m generic.Matcher, options *api.Li
|
|||||||
}
|
}
|
||||||
err = e.Storage.List(ctx, e.KeyRootFunc(ctx), version, filterFunc, list)
|
err = e.Storage.List(ctx, e.KeyRootFunc(ctx), version, filterFunc, list)
|
||||||
trace.Step("List extracted")
|
trace.Step("List extracted")
|
||||||
if err != nil {
|
return list, etcderr.InterpretListError(err, e.EndpointName)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create inserts a new item according to the unique key from the object.
|
// Create inserts a new item according to the unique key from the object.
|
||||||
|
@ -43,6 +43,16 @@ func IsEtcdTestFailed(err error) bool {
|
|||||||
return isEtcdErrorNum(err, tools.EtcdErrorCodeTestFailed)
|
return isEtcdErrorNum(err, tools.EtcdErrorCodeTestFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEtcdWatchExpired returns true if and only if err indicates the watch has expired.
|
||||||
|
func IsEtcdWatchExpired(err error) bool {
|
||||||
|
return isEtcdErrorNum(err, tools.EtcdErrorCodeWatchExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEtcdUnreachable returns true if and only if err indicates the server could not be reached.
|
||||||
|
func IsEtcdUnreachable(err error) bool {
|
||||||
|
return isEtcdErrorNum(err, tools.EtcdErrorCodeUnreachable)
|
||||||
|
}
|
||||||
|
|
||||||
// IsEtcdWatchStoppedByUser returns true if and only if err is a client triggered stop.
|
// IsEtcdWatchStoppedByUser returns true if and only if err is a client triggered stop.
|
||||||
func IsEtcdWatchStoppedByUser(err error) bool {
|
func IsEtcdWatchStoppedByUser(err error) bool {
|
||||||
return goetcd.ErrWatchStoppedByUser == err
|
return goetcd.ErrWatchStoppedByUser == err
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package etcd
|
package etcd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -181,12 +182,30 @@ func (w *etcdWatcher) translate() {
|
|||||||
select {
|
select {
|
||||||
case err := <-w.etcdError:
|
case err := <-w.etcdError:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.emit(watch.Event{
|
var status *unversioned.Status
|
||||||
Type: watch.Error,
|
switch {
|
||||||
Object: &unversioned.Status{
|
case IsEtcdWatchExpired(err):
|
||||||
|
status = &unversioned.Status{
|
||||||
Status: unversioned.StatusFailure,
|
Status: unversioned.StatusFailure,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
},
|
Code: http.StatusGone, // Gone
|
||||||
|
Reason: unversioned.StatusReasonExpired,
|
||||||
|
}
|
||||||
|
// TODO: need to generate errors using api/errors which has a circular dependency on this package
|
||||||
|
// no other way to inject errors
|
||||||
|
// case IsEtcdUnreachable(err):
|
||||||
|
// status = errors.NewServerTimeout(...)
|
||||||
|
default:
|
||||||
|
status = &unversioned.Status{
|
||||||
|
Status: unversioned.StatusFailure,
|
||||||
|
Message: err.Error(),
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Reason: unversioned.StatusReasonInternalError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.emit(watch.Event{
|
||||||
|
Type: watch.Error,
|
||||||
|
Object: status,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -25,6 +25,8 @@ const (
|
|||||||
EtcdErrorCodeTestFailed = 101
|
EtcdErrorCodeTestFailed = 101
|
||||||
EtcdErrorCodeNodeExist = 105
|
EtcdErrorCodeNodeExist = 105
|
||||||
EtcdErrorCodeValueRequired = 200
|
EtcdErrorCodeValueRequired = 200
|
||||||
|
EtcdErrorCodeWatchExpired = 401
|
||||||
|
EtcdErrorCodeUnreachable = 501
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -32,6 +34,8 @@ var (
|
|||||||
EtcdErrorTestFailed = &etcd.EtcdError{ErrorCode: EtcdErrorCodeTestFailed}
|
EtcdErrorTestFailed = &etcd.EtcdError{ErrorCode: EtcdErrorCodeTestFailed}
|
||||||
EtcdErrorNodeExist = &etcd.EtcdError{ErrorCode: EtcdErrorCodeNodeExist}
|
EtcdErrorNodeExist = &etcd.EtcdError{ErrorCode: EtcdErrorCodeNodeExist}
|
||||||
EtcdErrorValueRequired = &etcd.EtcdError{ErrorCode: EtcdErrorCodeValueRequired}
|
EtcdErrorValueRequired = &etcd.EtcdError{ErrorCode: EtcdErrorCodeValueRequired}
|
||||||
|
EtcdErrorWatchExpired = &etcd.EtcdError{ErrorCode: EtcdErrorCodeWatchExpired}
|
||||||
|
EtcdErrorUnreachable = &etcd.EtcdError{ErrorCode: EtcdErrorCodeUnreachable}
|
||||||
)
|
)
|
||||||
|
|
||||||
// EtcdClient is an injectable interface for testing.
|
// EtcdClient is an injectable interface for testing.
|
||||||
|
Loading…
Reference in New Issue
Block a user