2013-10-07 20:44:51 +04:00
|
|
|
/*
|
|
|
|
Copyright 2013 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.
|
|
|
|
*/
|
|
|
|
|
2013-08-18 07:41:15 +04:00
|
|
|
package error
|
2013-07-12 21:42:07 +04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2013-10-06 22:23:52 +04:00
|
|
|
"fmt"
|
2013-08-18 07:41:15 +04:00
|
|
|
"net/http"
|
2013-07-12 21:42:07 +04:00
|
|
|
)
|
|
|
|
|
2014-03-26 07:16:43 +04:00
|
|
|
var errors = map[int]string{
|
|
|
|
// command related errors
|
2014-04-18 02:36:51 +04:00
|
|
|
EcodeKeyNotFound: "Key not found",
|
|
|
|
EcodeTestFailed: "Compare failed", //test and set
|
|
|
|
EcodeNotFile: "Not a file",
|
|
|
|
EcodeNoMorePeer: "Reached the max number of peers in the cluster",
|
|
|
|
EcodeNotDir: "Not a directory",
|
|
|
|
EcodeNodeExist: "Key already exists", // create
|
|
|
|
EcodeRootROnly: "Root is read only",
|
|
|
|
EcodeKeyIsPreserved: "The prefix of given key is a keyword in etcd",
|
|
|
|
EcodeDirNotEmpty: "Directory not empty",
|
|
|
|
EcodeExistingPeerAddr: "Peer address has existed",
|
2014-03-26 07:16:43 +04:00
|
|
|
|
|
|
|
// Post form related errors
|
|
|
|
EcodeValueRequired: "Value is Required in POST form",
|
|
|
|
EcodePrevValueRequired: "PrevValue is Required in POST form",
|
|
|
|
EcodeTTLNaN: "The given TTL in POST form is not a number",
|
|
|
|
EcodeIndexNaN: "The given index in POST form is not a number",
|
|
|
|
EcodeValueOrTTLRequired: "Value or TTL is required in POST form",
|
|
|
|
EcodeTimeoutNaN: "The given timeout in POST form is not a number",
|
|
|
|
EcodeNameRequired: "Name is required in POST form",
|
|
|
|
EcodeIndexOrValueRequired: "Index or value is required",
|
|
|
|
EcodeIndexValueMutex: "Index and value cannot both be specified",
|
|
|
|
EcodeInvalidField: "Invalid field",
|
|
|
|
|
|
|
|
// raft related errors
|
|
|
|
EcodeRaftInternal: "Raft Internal Error",
|
|
|
|
EcodeLeaderElect: "During Leader Election",
|
|
|
|
|
|
|
|
// etcd related errors
|
2014-05-09 06:47:19 +04:00
|
|
|
EcodeWatcherCleared: "watcher is cleared due to etcd recovery",
|
|
|
|
EcodeEventIndexCleared: "The event in requested index is outdated and cleared",
|
|
|
|
EcodeStandbyInternal: "Standby Internal Error",
|
|
|
|
EcodeInvalidActiveSize: "Invalid active size",
|
|
|
|
EcodeInvalidRemoveDelay: "Standby remove delay",
|
2014-05-02 01:10:59 +04:00
|
|
|
|
|
|
|
// client related errors
|
|
|
|
EcodeClientInternal: "Client Internal Error",
|
2014-03-26 07:16:43 +04:00
|
|
|
}
|
2013-07-13 00:35:43 +04:00
|
|
|
|
2013-09-09 02:46:16 +04:00
|
|
|
const (
|
2014-04-18 02:36:51 +04:00
|
|
|
EcodeKeyNotFound = 100
|
|
|
|
EcodeTestFailed = 101
|
|
|
|
EcodeNotFile = 102
|
|
|
|
EcodeNoMorePeer = 103
|
|
|
|
EcodeNotDir = 104
|
|
|
|
EcodeNodeExist = 105
|
|
|
|
EcodeKeyIsPreserved = 106
|
|
|
|
EcodeRootROnly = 107
|
|
|
|
EcodeDirNotEmpty = 108
|
|
|
|
EcodeExistingPeerAddr = 109
|
2013-09-09 02:46:16 +04:00
|
|
|
|
2014-01-14 01:13:41 +04:00
|
|
|
EcodeValueRequired = 200
|
|
|
|
EcodePrevValueRequired = 201
|
|
|
|
EcodeTTLNaN = 202
|
|
|
|
EcodeIndexNaN = 203
|
|
|
|
EcodeValueOrTTLRequired = 204
|
|
|
|
EcodeTimeoutNaN = 205
|
|
|
|
EcodeNameRequired = 206
|
|
|
|
EcodeIndexOrValueRequired = 207
|
|
|
|
EcodeIndexValueMutex = 208
|
|
|
|
EcodeInvalidField = 209
|
2013-09-09 02:46:16 +04:00
|
|
|
|
|
|
|
EcodeRaftInternal = 300
|
|
|
|
EcodeLeaderElect = 301
|
|
|
|
|
2014-05-09 06:47:19 +04:00
|
|
|
EcodeWatcherCleared = 400
|
|
|
|
EcodeEventIndexCleared = 401
|
|
|
|
EcodeStandbyInternal = 402
|
|
|
|
EcodeInvalidActiveSize = 403
|
|
|
|
EcodeInvalidRemoveDelay = 404
|
2014-05-02 01:10:59 +04:00
|
|
|
|
|
|
|
EcodeClientInternal = 500
|
2013-09-09 02:46:16 +04:00
|
|
|
)
|
2013-08-18 07:41:15 +04:00
|
|
|
|
|
|
|
type Error struct {
|
2013-07-13 00:35:43 +04:00
|
|
|
ErrorCode int `json:"errorCode"`
|
|
|
|
Message string `json:"message"`
|
|
|
|
Cause string `json:"cause,omitempty"`
|
2013-10-06 22:23:52 +04:00
|
|
|
Index uint64 `json:"index"`
|
2013-07-12 21:42:07 +04:00
|
|
|
}
|
|
|
|
|
2013-11-10 05:55:54 +04:00
|
|
|
func NewError(errorCode int, cause string, index uint64) *Error {
|
2013-10-06 22:23:52 +04:00
|
|
|
return &Error{
|
2013-07-13 00:35:43 +04:00
|
|
|
ErrorCode: errorCode,
|
|
|
|
Message: errors[errorCode],
|
|
|
|
Cause: cause,
|
2013-10-06 22:23:52 +04:00
|
|
|
Index: index,
|
2013-08-18 02:06:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-18 07:41:15 +04:00
|
|
|
func Message(code int) string {
|
|
|
|
return errors[code]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only for error interface
|
|
|
|
func (e Error) Error() string {
|
2014-05-02 01:10:59 +04:00
|
|
|
return e.Message + " (" + e.Cause + ")"
|
2013-08-18 07:41:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e Error) toJsonString() string {
|
2013-08-18 02:06:21 +04:00
|
|
|
b, _ := json.Marshal(e)
|
2013-08-18 07:41:15 +04:00
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Error) Write(w http.ResponseWriter) {
|
2013-10-06 22:23:52 +04:00
|
|
|
w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index))
|
2013-12-21 21:50:46 +04:00
|
|
|
// 3xx is raft internal error
|
feat(v2/errors): Use more appropriate HTTP status codes for error cases.
This commits adds test coverage for all the error and non-error cases
described below, but only the behavior of the 403, 404 and 412 cases
are changing in this commit.
When setting a key results in a new resource, we asset an HTTP status
code of 201 (aka "Created").
When attempting to get a resource that doesn't exist, we assert an
HTTP status code of 404 (aka "Not Found").
When attempting to delete a directory without dir=true, or a non-empty
directory without recursive=true, but the request is otherwise valid,
we assert an HTTP status code of 403 (aka "Forbidden").
When a precondition (e.g. specified by prevIndex, or prevValue) is not
met, but the request is otherwise syntactically valid, we assert an
HTTP status code of 412 (aka "Precondition Failed"). However,
prevExist is handled slightly differently. If prevExist=false fails,
then this is treated like a failed precondition, so it should use
PreconditionFailed. But, if prevExist=true fails, then this is
treated like other requests that require the existence of the
resource, and uses NotFound if the resource doesn't exist.
We continue to assert an HTTP status code of 400 when the request is
syntactically invalid (e.g. when prevIndex=bad_index).
2013-12-21 22:14:30 +04:00
|
|
|
status := http.StatusBadRequest
|
|
|
|
switch e.ErrorCode {
|
|
|
|
case EcodeKeyNotFound:
|
|
|
|
status = http.StatusNotFound
|
2014-04-22 04:55:44 +04:00
|
|
|
case EcodeNotFile, EcodeDirNotEmpty:
|
feat(v2/errors): Use more appropriate HTTP status codes for error cases.
This commits adds test coverage for all the error and non-error cases
described below, but only the behavior of the 403, 404 and 412 cases
are changing in this commit.
When setting a key results in a new resource, we asset an HTTP status
code of 201 (aka "Created").
When attempting to get a resource that doesn't exist, we assert an
HTTP status code of 404 (aka "Not Found").
When attempting to delete a directory without dir=true, or a non-empty
directory without recursive=true, but the request is otherwise valid,
we assert an HTTP status code of 403 (aka "Forbidden").
When a precondition (e.g. specified by prevIndex, or prevValue) is not
met, but the request is otherwise syntactically valid, we assert an
HTTP status code of 412 (aka "Precondition Failed"). However,
prevExist is handled slightly differently. If prevExist=false fails,
then this is treated like a failed precondition, so it should use
PreconditionFailed. But, if prevExist=true fails, then this is
treated like other requests that require the existence of the
resource, and uses NotFound if the resource doesn't exist.
We continue to assert an HTTP status code of 400 when the request is
syntactically invalid (e.g. when prevIndex=bad_index).
2013-12-21 22:14:30 +04:00
|
|
|
status = http.StatusForbidden
|
|
|
|
case EcodeTestFailed, EcodeNodeExist:
|
|
|
|
status = http.StatusPreconditionFailed
|
|
|
|
default:
|
|
|
|
if e.ErrorCode/100 == 3 {
|
|
|
|
status = http.StatusInternalServerError
|
|
|
|
}
|
2013-08-18 07:41:15 +04:00
|
|
|
}
|
2014-05-30 01:18:50 +04:00
|
|
|
w.WriteHeader(status)
|
|
|
|
fmt.Fprintln(w, e.toJsonString())
|
2013-07-12 21:42:07 +04:00
|
|
|
}
|