mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
client: introduce Error type
This commit is contained in:
parent
8fdc6b154e
commit
b174732812
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user