diff --git a/client/client.go b/client/client.go index 3c4368488..7ddcd845d 100644 --- a/client/client.go +++ b/client/client.go @@ -28,19 +28,12 @@ import ( ) var ( - ErrTimeout = context.DeadlineExceeded - ErrCanceled = context.Canceled - ErrUnavailable = errors.New("client: no available etcd endpoints") - ErrNoLeader = errors.New("client: no leader") - ErrNoEndpoints = errors.New("no endpoints available") - ErrTooManyRedirects = errors.New("too many redirects") - - ErrKeyNoExist = errors.New("client: key does not exist") - ErrKeyExists = errors.New("client: key already exists") - - DefaultRequestTimeout = 5 * time.Second + ErrNoEndpoints = errors.New("client: no endpoints available") + ErrTooManyRedirects = errors.New("client: too many redirects") ) +var DefaultRequestTimeout = 5 * time.Second + var DefaultTransport CancelableTransport = &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ @@ -203,7 +196,7 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (resp *http. hc := c.clientFactory(ep) resp, body, err = hc.Do(ctx, act) if err != nil { - if err == ErrTimeout || err == ErrCanceled { + if err == context.DeadlineExceeded || err == context.Canceled { return nil, nil, err } continue diff --git a/client/client_test.go b/client/client_test.go index f57ffb94c..71e9f5cc7 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -226,32 +226,32 @@ func TestHTTPClusterClientDo(t *testing.T) { wantCode: http.StatusTeapot, }, - // ErrTimeout short-circuits Do + // context.DeadlineExceeded short-circuits Do { client: &httpClusterClient{ endpoints: []url.URL{fakeURL, fakeURL}, clientFactory: newStaticHTTPClientFactory( []staticHTTPResponse{ - staticHTTPResponse{err: ErrTimeout}, + staticHTTPResponse{err: context.DeadlineExceeded}, staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}}, }, ), }, - wantErr: ErrTimeout, + wantErr: context.DeadlineExceeded, }, - // ErrCanceled short-circuits Do + // context.Canceled short-circuits Do { client: &httpClusterClient{ endpoints: []url.URL{fakeURL, fakeURL}, clientFactory: newStaticHTTPClientFactory( []staticHTTPResponse{ - staticHTTPResponse{err: ErrCanceled}, + staticHTTPResponse{err: context.Canceled}, staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}}, }, ), }, - wantErr: ErrCanceled, + wantErr: context.Canceled, }, // return err if there are no endpoints diff --git a/client/error.go b/client/error.go deleted file mode 100644 index 72c7edd0d..000000000 --- a/client/error.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -type HTTPError struct { - Message string `json:"message"` - Code int `json:"-"` -} - -func (e HTTPError) Error() string { - return e.Message -} diff --git a/client/keys.go b/client/keys.go index 668ff029d..e21bdaf8a 100644 --- a/client/keys.go +++ b/client/keys.go @@ -27,6 +27,39 @@ import ( "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" ) +const ( + ErrorCodeKeyNotFound = 100 + ErrorCodeTestFailed = 101 + ErrorCodeNotFile = 102 + ErrorCodeNotDir = 104 + ErrorCodeNodeExist = 105 + ErrorCodeRootROnly = 107 + ErrorCodeDirNotEmpty = 108 + + ErrorCodePrevValueRequired = 201 + ErrorCodeTTLNaN = 202 + ErrorCodeIndexNaN = 203 + ErrorCodeInvalidField = 209 + ErrorCodeInvalidForm = 210 + + ErrorCodeRaftInternal = 300 + ErrorCodeLeaderElect = 301 + + ErrorCodeWatcherCleared = 400 + ErrorCodeEventIndexCleared = 401 +) + +type Error struct { + Code int `json:"errorCode"` + Message string `json:"message"` + Cause string `json:"cause"` + Index uint64 `json:"index"` +} + +func (e Error) Error() string { + return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index) +} + // PrevExistType is used to define an existence condition when setting // or deleting Nodes. type PrevExistType string @@ -452,15 +485,15 @@ func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request { func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) { switch code { case http.StatusOK, http.StatusCreated: - res, err = unmarshalSuccessfulResponse(header, body) + res, err = unmarshalSuccessfulKeysResponse(header, body) default: - err = unmarshalErrorResponse(code) + err = unmarshalFailedKeysResponse(body) } return } -func unmarshalSuccessfulResponse(header http.Header, body []byte) (*Response, error) { +func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) { var res Response err := json.Unmarshal(body, &res) if err != nil { @@ -468,26 +501,17 @@ func unmarshalSuccessfulResponse(header http.Header, body []byte) (*Response, er } if header.Get("X-Etcd-Index") != "" { res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64) - } - if err != nil { - return nil, err + if err != nil { + return nil, err + } } return &res, nil } -func unmarshalErrorResponse(code int) error { - switch code { - case http.StatusNotFound: - return ErrKeyNoExist - case http.StatusPreconditionFailed: - return ErrKeyExists - case http.StatusInternalServerError: - // this isn't necessarily true - return ErrNoLeader - case http.StatusGatewayTimeout: - return ErrTimeout - default: +func unmarshalFailedKeysResponse(body []byte) error { + var etcdErr Error + if err := json.Unmarshal(body, &etcdErr); err != nil { + return err } - - return fmt.Errorf("unrecognized HTTP status code %d", code) + return etcdErr } diff --git a/client/keys_test.go b/client/keys_test.go index 1eab72519..6f14cfc47 100644 --- a/client/keys_test.go +++ b/client/keys_test.go @@ -15,7 +15,6 @@ package client import ( - "errors" "fmt" "io/ioutil" "net/http" @@ -531,7 +530,7 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) { for i, tt := range tests { h := make(http.Header) h.Add("X-Etcd-Index", tt.indexHeader) - res, err := unmarshalSuccessfulResponse(h, []byte(tt.body)) + res, err := unmarshalSuccessfulKeysResponse(h, []byte(tt.body)) if tt.expectError != (err != nil) { t.Errorf("#%d: expectError=%t, err=%v", i, tt.expectError, err) } @@ -555,51 +554,3 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) { } } } - -func TestUnmarshalErrorResponse(t *testing.T) { - unrecognized := errors.New("test fixture") - - tests := []struct { - code int - want error - }{ - {http.StatusBadRequest, unrecognized}, - {http.StatusUnauthorized, unrecognized}, - {http.StatusPaymentRequired, unrecognized}, - {http.StatusForbidden, unrecognized}, - {http.StatusNotFound, ErrKeyNoExist}, - {http.StatusMethodNotAllowed, unrecognized}, - {http.StatusNotAcceptable, unrecognized}, - {http.StatusProxyAuthRequired, unrecognized}, - {http.StatusRequestTimeout, unrecognized}, - {http.StatusConflict, unrecognized}, - {http.StatusGone, unrecognized}, - {http.StatusLengthRequired, unrecognized}, - {http.StatusPreconditionFailed, ErrKeyExists}, - {http.StatusRequestEntityTooLarge, unrecognized}, - {http.StatusRequestURITooLong, unrecognized}, - {http.StatusUnsupportedMediaType, unrecognized}, - {http.StatusRequestedRangeNotSatisfiable, unrecognized}, - {http.StatusExpectationFailed, unrecognized}, - {http.StatusTeapot, unrecognized}, - - {http.StatusInternalServerError, ErrNoLeader}, - {http.StatusNotImplemented, unrecognized}, - {http.StatusBadGateway, unrecognized}, - {http.StatusServiceUnavailable, unrecognized}, - {http.StatusGatewayTimeout, ErrTimeout}, - {http.StatusHTTPVersionNotSupported, unrecognized}, - } - - for i, tt := range tests { - want := tt.want - if reflect.DeepEqual(unrecognized, want) { - want = fmt.Errorf("unrecognized HTTP status code %d", tt.code) - } - - got := unmarshalErrorResponse(tt.code) - if !reflect.DeepEqual(want, got) { - t.Errorf("#%d: want=%v, got=%v", i, want, got) - } - } -} diff --git a/client/members.go b/client/members.go index 36a659a48..a5caf2bd3 100644 --- a/client/members.go +++ b/client/members.go @@ -144,11 +144,11 @@ func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, erro } if resp.StatusCode != http.StatusCreated { - var httperr HTTPError - if err := json.Unmarshal(body, &httperr); err != nil { + var merr membersError + if err := json.Unmarshal(body, &merr); err != nil { return nil, err } - return nil, httperr + return nil, merr } var memb Member @@ -216,3 +216,12 @@ func v2MembersURL(ep url.URL) *url.URL { ep.Path = path.Join(ep.Path, defaultV2MembersPrefix) return &ep } + +type membersError struct { + Message string `json:"message"` + Code int `json:"-"` +} + +func (e membersError) Error() string { + return e.Message +} diff --git a/discovery/discovery.go b/discovery/discovery.go index cdd315286..e7bdf5ff4 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -183,7 +183,7 @@ func (d *discovery) createSelf(contents string) error { resp, err := d.c.Create(ctx, d.selfKey(), contents) cancel() if err != nil { - if err == client.ErrKeyExists { + if eerr, ok := err.(*client.Error); ok && eerr.Code == client.ErrorCodeNodeExist { return ErrDuplicateID } return err @@ -202,10 +202,10 @@ func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) { resp, err := d.c.Get(ctx, path.Join(configKey, "size"), nil) cancel() if err != nil { - if err == client.ErrKeyNoExist { + if eerr, ok := err.(*client.Error); ok && eerr.Code == client.ErrorCodeKeyNotFound { return nil, 0, 0, ErrSizeNotFound } - if err == client.ErrTimeout { + if err == context.DeadlineExceeded { return d.checkClusterRetry() } return nil, 0, 0, err @@ -219,7 +219,7 @@ func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) { resp, err = d.c.Get(ctx, d.cluster, nil) cancel() if err != nil { - if err == client.ErrTimeout { + if err == context.DeadlineExceeded { return d.checkClusterRetry() } return nil, 0, 0, err @@ -295,7 +295,7 @@ func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]* log.Printf("discovery: found %d peer(s), waiting for %d more", len(all), size-len(all)) resp, err := w.Next(context.Background()) if err != nil { - if err == client.ErrTimeout { + if err == context.DeadlineExceeded { return d.waitNodesRetry() } return nil, err diff --git a/discovery/discovery_test.go b/discovery/discovery_test.go index 0d1b84e1b..5f2ba641e 100644 --- a/discovery/discovery_test.go +++ b/discovery/discovery_test.go @@ -424,7 +424,7 @@ func (c *clientWithResp) Create(ctx context.Context, key string, value string) ( func (c *clientWithResp) Get(ctx context.Context, key string, opts *client.GetOptions) (*client.Response, error) { if len(c.rs) == 0 { - return &client.Response{}, client.ErrKeyNoExist + return &client.Response{}, &client.Error{Code: client.ErrorCodeKeyNotFound} } r := c.rs[0] c.rs = append(c.rs[1:], r) @@ -485,7 +485,7 @@ type clientWithRetry struct { func (c *clientWithRetry) Create(ctx context.Context, key string, value string) (*client.Response, error) { if c.failCount < c.failTimes { c.failCount++ - return nil, client.ErrTimeout + return nil, context.DeadlineExceeded } return c.clientWithResp.Create(ctx, key, value) } @@ -493,7 +493,7 @@ func (c *clientWithRetry) Create(ctx context.Context, key string, value string) func (c *clientWithRetry) Get(ctx context.Context, key string, opts *client.GetOptions) (*client.Response, error) { if c.failCount < c.failTimes { c.failCount++ - return nil, client.ErrTimeout + return nil, context.DeadlineExceeded } return c.clientWithResp.Get(ctx, key, opts) } @@ -508,7 +508,7 @@ type watcherWithRetry struct { func (w *watcherWithRetry) Next(context.Context) (*client.Response, error) { if w.failCount < w.failTimes { w.failCount++ - return nil, client.ErrTimeout + return nil, context.DeadlineExceeded } if len(w.rs) == 0 { return &client.Response{}, nil