1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
package apierrors
import (
"io"
"io/ioutil"
"net/http"
)
// Code is a Mesos HTTP v1 API response status code
type Code int
const (
// MsgNotLeader is returned by Do calls that are sent to a non leading Mesos master.
MsgNotLeader = "call sent to a non-leading master"
// MsgAuth is returned by Do calls that are not successfully authenticated.
MsgAuth = "call not authenticated"
// MsgUnsubscribed is returned by Do calls that are sent before a subscription is established.
MsgUnsubscribed = "no subscription established"
// MsgVersion is returned by Do calls that are sent to an incompatible API version.
MsgVersion = "incompatible API version"
// MsgMalformed is returned by Do calls that are malformed.
MsgMalformed = "malformed request"
// MsgMediaType is returned by Do calls that are sent with an unsupported media type.
MsgMediaType = "unsupported media type"
// MsgRateLimit is returned by Do calls that are rate limited. This is a temporary condition
// that should clear.
MsgRateLimit = "rate limited"
// MsgUnavailable is returned by Do calls that are sent to a master or agent that's in recovery, or
// does not yet realize that it's the leader. This is a temporary condition that should clear.
MsgUnavailable = "mesos server unavailable"
// MsgNotFound could happen if the master or agent libprocess has not yet set up http routes.
MsgNotFound = "mesos http endpoint not found"
CodeNotLeader = Code(http.StatusTemporaryRedirect)
CodeNotAuthenticated = Code(http.StatusUnauthorized)
CodeUnsubscribed = Code(http.StatusForbidden)
CodeIncompatibleVersion = Code(http.StatusConflict)
CodeMalformedRequest = Code(http.StatusBadRequest)
CodeUnsupportedMediaType = Code(http.StatusNotAcceptable)
CodeRateLimitExceeded = Code(http.StatusTooManyRequests)
CodeMesosUnavailable = Code(http.StatusServiceUnavailable)
CodeNotFound = Code(http.StatusNotFound)
MaxSizeDetails = 4 * 1024 // MaxSizeDetails limits the length of the details message read from a response body
)
var (
// ErrorTable maps HTTP response codes to their respective Mesos v1 API error messages.
ErrorTable = map[Code]string{
CodeNotLeader: MsgNotLeader,
CodeMalformedRequest: MsgMalformed,
CodeIncompatibleVersion: MsgVersion,
CodeUnsubscribed: MsgUnsubscribed,
CodeNotAuthenticated: MsgAuth,
CodeUnsupportedMediaType: MsgMediaType,
CodeNotFound: MsgNotFound,
CodeMesosUnavailable: MsgUnavailable,
CodeRateLimitExceeded: MsgRateLimit,
}
)
// Error captures HTTP v1 API error codes and messages generated by Mesos.
type Error struct {
code Code // code is the HTTP response status code generated by Mesos
message string // message briefly summarizes the nature of the error, possibly includes details from Mesos
}
// IsError returns true for all HTTP status codes that are not considered informational or successful.
func (code Code) IsError() bool {
return code >= 300
}
// FromResponse returns an `*Error` for a response containing a status code that indicates an error condition.
// The response body (if any) is captured in the Error.Details field.
// Returns nil for nil responses and responses with non-error status codes.
// See IsErrorCode.
func FromResponse(res *http.Response) error {
if res == nil {
return nil
}
code := Code(res.StatusCode)
if !code.IsError() {
// non-error HTTP response codes don't generate errors
return nil
}
var details string
if res.Body != nil {
defer res.Body.Close()
buf, _ := ioutil.ReadAll(io.LimitReader(res.Body, MaxSizeDetails))
details = string(buf)
}
return code.Error(details)
}
// Error generates an error from the given status code and detail string.
func (code Code) Error(details string) error {
if !code.IsError() {
return nil
}
err := &Error{
code: code,
message: ErrorTable[code],
}
if details != "" {
err.message = err.message + ": " + details
}
return err
}
// Error implements error interface
func (e *Error) Error() string { return e.message }
// Temporary returns true if the error is a temporary condition that should eventually clear.
func (e *Error) Temporary() bool {
switch e.code {
// TODO(jdef): NotFound **could** be a temporary error because there's a race at mesos startup in which the
// HTTP server responds before the internal listeners have been initialized. But it could also be reported
// because the client is accessing an invalid endpoint; as of right now, a client cannot distinguish between
// these cases.
// https://issues.apache.org/jira/browse/MESOS-7697
case CodeRateLimitExceeded, CodeMesosUnavailable:
return true
default:
return false
}
}
// CodesIndicatingSubscriptionLoss is a set of apierror.Code entries which each indicate that
// the event subscription stream has been severed between the scheduler and mesos. It's respresented
// as a public map variable so that clients can program additional error codes (if such are discovered)
// without hacking the code of the mesos-go library directly.
var CodesIndicatingSubscriptionLoss = func(codes ...Code) map[Code]struct{} {
result := make(map[Code]struct{}, len(codes))
for _, code := range codes {
result[code] = struct{}{}
}
return result
}(
// expand this list as we discover other errors that guarantee we've lost our event subscription.
CodeUnsubscribed,
)
// SubscriptionLoss returns true if the error indicates that the event subscription stream has been severed
// between mesos and a mesos client.
func (e *Error) SubscriptionLoss() (result bool) {
_, result = CodesIndicatingSubscriptionLoss[e.code]
return
}
// Matches returns true if the given error is an API error with a matching error code
func (code Code) Matches(err error) bool {
if err == nil {
return !code.IsError()
}
apiErr, ok := err.(*Error)
return ok && apiErr.code == code
}
|