client: introduce Error type

release-2.1
Brian Waldon 2015-01-28 16:46:58 -08:00 committed by Yicheng Qin
parent 8fdc6b154e
commit b174732812
8 changed files with 77 additions and 124 deletions

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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