2 Copyright 2014 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/util/validation/field"
33 // StatusTooManyRequests means the server experienced too many requests within a
34 // given window and that the client must wait to perform the action again.
35 StatusTooManyRequests = 429
38 // StatusError is an error intended for consumption by a REST API server; it can also be
39 // reconstructed by clients from a REST response. Public to allow easy type switches.
40 type StatusError struct {
41 ErrStatus metav1.Status
44 // APIStatus is exposed by errors that can be converted to an api.Status object
45 // for finer grained details.
46 type APIStatus interface {
47 Status() metav1.Status
50 var _ error = &StatusError{}
52 // Error implements the Error interface.
53 func (e *StatusError) Error() string {
54 return e.ErrStatus.Message
57 // Status allows access to e's status without having to know the detailed workings
59 func (e *StatusError) Status() metav1.Status {
63 // DebugError reports extended info about the error to debug output.
64 func (e *StatusError) DebugError() (string, []interface{}) {
65 if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil {
66 return "server response object: %s", []interface{}{string(out)}
68 return "server response object: %#v", []interface{}{e.ErrStatus}
71 // UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
72 type UnexpectedObjectError struct {
76 // Error returns an error message describing 'u'.
77 func (u *UnexpectedObjectError) Error() string {
78 return fmt.Sprintf("unexpected object: %v", u.Object)
81 // FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise,
82 // returns an UnexpecteObjectError.
83 func FromObject(obj runtime.Object) error {
84 switch t := obj.(type) {
86 return &StatusError{ErrStatus: *t}
87 case runtime.Unstructured:
88 var status metav1.Status
89 obj := t.UnstructuredContent()
90 if !reflect.DeepEqual(obj["kind"], "Status") {
93 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(t.UnstructuredContent(), &status); err != nil {
96 if status.APIVersion != "v1" && status.APIVersion != "meta.k8s.io/v1" {
99 return &StatusError{ErrStatus: status}
101 return &UnexpectedObjectError{obj}
104 // NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
105 func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError {
106 return &StatusError{metav1.Status{
107 Status: metav1.StatusFailure,
108 Code: http.StatusNotFound,
109 Reason: metav1.StatusReasonNotFound,
110 Details: &metav1.StatusDetails{
111 Group: qualifiedResource.Group,
112 Kind: qualifiedResource.Resource,
115 Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name),
119 // NewAlreadyExists returns an error indicating the item requested exists by that identifier.
120 func NewAlreadyExists(qualifiedResource schema.GroupResource, name string) *StatusError {
121 return &StatusError{metav1.Status{
122 Status: metav1.StatusFailure,
123 Code: http.StatusConflict,
124 Reason: metav1.StatusReasonAlreadyExists,
125 Details: &metav1.StatusDetails{
126 Group: qualifiedResource.Group,
127 Kind: qualifiedResource.Resource,
130 Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name),
134 // NewUnauthorized returns an error indicating the client is not authorized to perform the requested
136 func NewUnauthorized(reason string) *StatusError {
138 if len(message) == 0 {
139 message = "not authorized"
141 return &StatusError{metav1.Status{
142 Status: metav1.StatusFailure,
143 Code: http.StatusUnauthorized,
144 Reason: metav1.StatusReasonUnauthorized,
149 // NewForbidden returns an error indicating the requested action was forbidden
150 func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
152 if qualifiedResource.Empty() {
153 message = fmt.Sprintf("forbidden: %v", err)
154 } else if name == "" {
155 message = fmt.Sprintf("%s is forbidden: %v", qualifiedResource.String(), err)
157 message = fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err)
159 return &StatusError{metav1.Status{
160 Status: metav1.StatusFailure,
161 Code: http.StatusForbidden,
162 Reason: metav1.StatusReasonForbidden,
163 Details: &metav1.StatusDetails{
164 Group: qualifiedResource.Group,
165 Kind: qualifiedResource.Resource,
172 // NewConflict returns an error indicating the item can't be updated as provided.
173 func NewConflict(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
174 return &StatusError{metav1.Status{
175 Status: metav1.StatusFailure,
176 Code: http.StatusConflict,
177 Reason: metav1.StatusReasonConflict,
178 Details: &metav1.StatusDetails{
179 Group: qualifiedResource.Group,
180 Kind: qualifiedResource.Resource,
183 Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err),
187 // NewGone returns an error indicating the item no longer available at the server and no forwarding address is known.
188 func NewGone(message string) *StatusError {
189 return &StatusError{metav1.Status{
190 Status: metav1.StatusFailure,
191 Code: http.StatusGone,
192 Reason: metav1.StatusReasonGone,
197 // NewResourceExpired creates an error that indicates that the requested resource content has expired from
198 // the server (usually due to a resourceVersion that is too old).
199 func NewResourceExpired(message string) *StatusError {
200 return &StatusError{metav1.Status{
201 Status: metav1.StatusFailure,
202 Code: http.StatusGone,
203 Reason: metav1.StatusReasonExpired,
208 // NewInvalid returns an error indicating the item is invalid and cannot be processed.
209 func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError {
210 causes := make([]metav1.StatusCause, 0, len(errs))
211 for i := range errs {
213 causes = append(causes, metav1.StatusCause{
214 Type: metav1.CauseType(err.Type),
215 Message: err.ErrorBody(),
219 return &StatusError{metav1.Status{
220 Status: metav1.StatusFailure,
221 Code: http.StatusUnprocessableEntity,
222 Reason: metav1.StatusReasonInvalid,
223 Details: &metav1.StatusDetails{
224 Group: qualifiedKind.Group,
225 Kind: qualifiedKind.Kind,
229 Message: fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, errs.ToAggregate()),
233 // NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
234 func NewBadRequest(reason string) *StatusError {
235 return &StatusError{metav1.Status{
236 Status: metav1.StatusFailure,
237 Code: http.StatusBadRequest,
238 Reason: metav1.StatusReasonBadRequest,
243 // NewTooManyRequests creates an error that indicates that the client must try again later because
244 // the specified endpoint is not accepting requests. More specific details should be provided
245 // if client should know why the failure was limited4.
246 func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError {
247 return &StatusError{metav1.Status{
248 Status: metav1.StatusFailure,
249 Code: http.StatusTooManyRequests,
250 Reason: metav1.StatusReasonTooManyRequests,
252 Details: &metav1.StatusDetails{
253 RetryAfterSeconds: int32(retryAfterSeconds),
258 // NewServiceUnavailable creates an error that indicates that the requested service is unavailable.
259 func NewServiceUnavailable(reason string) *StatusError {
260 return &StatusError{metav1.Status{
261 Status: metav1.StatusFailure,
262 Code: http.StatusServiceUnavailable,
263 Reason: metav1.StatusReasonServiceUnavailable,
268 // NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
269 func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError {
270 return &StatusError{metav1.Status{
271 Status: metav1.StatusFailure,
272 Code: http.StatusMethodNotAllowed,
273 Reason: metav1.StatusReasonMethodNotAllowed,
274 Details: &metav1.StatusDetails{
275 Group: qualifiedResource.Group,
276 Kind: qualifiedResource.Resource,
278 Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()),
282 // NewServerTimeout returns an error indicating the requested action could not be completed due to a
283 // transient error, and the client should try again.
284 func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError {
285 return &StatusError{metav1.Status{
286 Status: metav1.StatusFailure,
287 Code: http.StatusInternalServerError,
288 Reason: metav1.StatusReasonServerTimeout,
289 Details: &metav1.StatusDetails{
290 Group: qualifiedResource.Group,
291 Kind: qualifiedResource.Resource,
293 RetryAfterSeconds: int32(retryAfterSeconds),
295 Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()),
299 // NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we
300 // happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this.
301 func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError {
302 return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds)
305 // NewInternalError returns an error indicating the item is invalid and cannot be processed.
306 func NewInternalError(err error) *StatusError {
307 return &StatusError{metav1.Status{
308 Status: metav1.StatusFailure,
309 Code: http.StatusInternalServerError,
310 Reason: metav1.StatusReasonInternalError,
311 Details: &metav1.StatusDetails{
312 Causes: []metav1.StatusCause{{Message: err.Error()}},
314 Message: fmt.Sprintf("Internal error occurred: %v", err),
318 // NewTimeoutError returns an error indicating that a timeout occurred before the request
319 // could be completed. Clients may retry, but the operation may still complete.
320 func NewTimeoutError(message string, retryAfterSeconds int) *StatusError {
321 return &StatusError{metav1.Status{
322 Status: metav1.StatusFailure,
323 Code: http.StatusGatewayTimeout,
324 Reason: metav1.StatusReasonTimeout,
325 Message: fmt.Sprintf("Timeout: %s", message),
326 Details: &metav1.StatusDetails{
327 RetryAfterSeconds: int32(retryAfterSeconds),
332 // NewTooManyRequestsError returns an error indicating that the request was rejected because
333 // the server has received too many requests. Client should wait and retry. But if the request
334 // is perishable, then the client should not retry the request.
335 func NewTooManyRequestsError(message string) *StatusError {
336 return &StatusError{metav1.Status{
337 Status: metav1.StatusFailure,
338 Code: StatusTooManyRequests,
339 Reason: metav1.StatusReasonTooManyRequests,
340 Message: fmt.Sprintf("Too many requests: %s", message),
344 // NewRequestEntityTooLargeError returns an error indicating that the request
345 // entity was too large.
346 func NewRequestEntityTooLargeError(message string) *StatusError {
347 return &StatusError{metav1.Status{
348 Status: metav1.StatusFailure,
349 Code: http.StatusRequestEntityTooLarge,
350 Reason: metav1.StatusReasonRequestEntityTooLarge,
351 Message: fmt.Sprintf("Request entity too large: %s", message),
355 // NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
356 func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
357 reason := metav1.StatusReasonUnknown
358 message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
360 case http.StatusConflict:
362 reason = metav1.StatusReasonAlreadyExists
364 reason = metav1.StatusReasonConflict
366 message = "the server reported a conflict"
367 case http.StatusNotFound:
368 reason = metav1.StatusReasonNotFound
369 message = "the server could not find the requested resource"
370 case http.StatusBadRequest:
371 reason = metav1.StatusReasonBadRequest
372 message = "the server rejected our request for an unknown reason"
373 case http.StatusUnauthorized:
374 reason = metav1.StatusReasonUnauthorized
375 message = "the server has asked for the client to provide credentials"
376 case http.StatusForbidden:
377 reason = metav1.StatusReasonForbidden
378 // the server message has details about who is trying to perform what action. Keep its message.
379 message = serverMessage
380 case http.StatusNotAcceptable:
381 reason = metav1.StatusReasonNotAcceptable
382 // the server message has details about what types are acceptable
383 message = serverMessage
384 case http.StatusUnsupportedMediaType:
385 reason = metav1.StatusReasonUnsupportedMediaType
386 // the server message has details about what types are acceptable
387 message = serverMessage
388 case http.StatusMethodNotAllowed:
389 reason = metav1.StatusReasonMethodNotAllowed
390 message = "the server does not allow this method on the requested resource"
391 case http.StatusUnprocessableEntity:
392 reason = metav1.StatusReasonInvalid
393 message = "the server rejected our request due to an error in our request"
394 case http.StatusServiceUnavailable:
395 reason = metav1.StatusReasonServiceUnavailable
396 message = "the server is currently unable to handle the request"
397 case http.StatusGatewayTimeout:
398 reason = metav1.StatusReasonTimeout
399 message = "the server was unable to return a response in the time allotted, but may still be processing the request"
400 case http.StatusTooManyRequests:
401 reason = metav1.StatusReasonTooManyRequests
402 message = "the server has received too many requests and has asked us to try again later"
405 reason = metav1.StatusReasonInternalError
406 message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage)
410 case !qualifiedResource.Empty() && len(name) > 0:
411 message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name)
412 case !qualifiedResource.Empty():
413 message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String())
415 var causes []metav1.StatusCause
416 if isUnexpectedResponse {
417 causes = []metav1.StatusCause{
419 Type: metav1.CauseTypeUnexpectedServerResponse,
420 Message: serverMessage,
426 return &StatusError{metav1.Status{
427 Status: metav1.StatusFailure,
430 Details: &metav1.StatusDetails{
431 Group: qualifiedResource.Group,
432 Kind: qualifiedResource.Resource,
436 RetryAfterSeconds: int32(retryAfterSeconds),
442 // IsNotFound returns true if the specified error was created by NewNotFound.
443 func IsNotFound(err error) bool {
444 return ReasonForError(err) == metav1.StatusReasonNotFound
447 // IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
448 func IsAlreadyExists(err error) bool {
449 return ReasonForError(err) == metav1.StatusReasonAlreadyExists
452 // IsConflict determines if the err is an error which indicates the provided update conflicts.
453 func IsConflict(err error) bool {
454 return ReasonForError(err) == metav1.StatusReasonConflict
457 // IsInvalid determines if the err is an error which indicates the provided resource is not valid.
458 func IsInvalid(err error) bool {
459 return ReasonForError(err) == metav1.StatusReasonInvalid
462 // IsGone is true if the error indicates the requested resource is no longer available.
463 func IsGone(err error) bool {
464 return ReasonForError(err) == metav1.StatusReasonGone
467 // IsResourceExpired is true if the error indicates the resource has expired and the current action is
468 // no longer possible.
469 func IsResourceExpired(err error) bool {
470 return ReasonForError(err) == metav1.StatusReasonExpired
473 // IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header
474 func IsNotAcceptable(err error) bool {
475 return ReasonForError(err) == metav1.StatusReasonNotAcceptable
478 // IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header
479 func IsUnsupportedMediaType(err error) bool {
480 return ReasonForError(err) == metav1.StatusReasonUnsupportedMediaType
483 // IsMethodNotSupported determines if the err is an error which indicates the provided action could not
484 // be performed because it is not supported by the server.
485 func IsMethodNotSupported(err error) bool {
486 return ReasonForError(err) == metav1.StatusReasonMethodNotAllowed
489 // IsServiceUnavailable is true if the error indicates the underlying service is no longer available.
490 func IsServiceUnavailable(err error) bool {
491 return ReasonForError(err) == metav1.StatusReasonServiceUnavailable
494 // IsBadRequest determines if err is an error which indicates that the request is invalid.
495 func IsBadRequest(err error) bool {
496 return ReasonForError(err) == metav1.StatusReasonBadRequest
499 // IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
500 // requires authentication by the user.
501 func IsUnauthorized(err error) bool {
502 return ReasonForError(err) == metav1.StatusReasonUnauthorized
505 // IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
506 // be completed as requested.
507 func IsForbidden(err error) bool {
508 return ReasonForError(err) == metav1.StatusReasonForbidden
511 // IsTimeout determines if err is an error which indicates that request times out due to long
513 func IsTimeout(err error) bool {
514 return ReasonForError(err) == metav1.StatusReasonTimeout
517 // IsServerTimeout determines if err is an error which indicates that the request needs to be retried
519 func IsServerTimeout(err error) bool {
520 return ReasonForError(err) == metav1.StatusReasonServerTimeout
523 // IsInternalError determines if err is an error which indicates an internal server error.
524 func IsInternalError(err error) bool {
525 return ReasonForError(err) == metav1.StatusReasonInternalError
528 // IsTooManyRequests determines if err is an error which indicates that there are too many requests
529 // that the server cannot handle.
530 func IsTooManyRequests(err error) bool {
531 if ReasonForError(err) == metav1.StatusReasonTooManyRequests {
534 switch t := err.(type) {
536 return t.Status().Code == http.StatusTooManyRequests
541 // IsRequestEntityTooLargeError determines if err is an error which indicates
542 // the request entity is too large.
543 func IsRequestEntityTooLargeError(err error) bool {
544 if ReasonForError(err) == metav1.StatusReasonRequestEntityTooLarge {
547 switch t := err.(type) {
549 return t.Status().Code == http.StatusRequestEntityTooLarge
554 // IsUnexpectedServerError returns true if the server response was not in the expected API format,
555 // and may be the result of another HTTP actor.
556 func IsUnexpectedServerError(err error) bool {
557 switch t := err.(type) {
559 if d := t.Status().Details; d != nil {
560 for _, cause := range d.Causes {
561 if cause.Type == metav1.CauseTypeUnexpectedServerResponse {
570 // IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
571 func IsUnexpectedObjectError(err error) bool {
572 _, ok := err.(*UnexpectedObjectError)
573 return err != nil && ok
576 // SuggestsClientDelay returns true if this error suggests a client delay as well as the
577 // suggested seconds to wait, or false if the error does not imply a wait. It does not
578 // address whether the error *should* be retried, since some errors (like a 3xx) may
579 // request delay without retry.
580 func SuggestsClientDelay(err error) (int, bool) {
581 switch t := err.(type) {
583 if t.Status().Details != nil {
584 switch t.Status().Reason {
585 // this StatusReason explicitly requests the caller to delay the action
586 case metav1.StatusReasonServerTimeout:
587 return int(t.Status().Details.RetryAfterSeconds), true
589 // If the client requests that we retry after a certain number of seconds
590 if t.Status().Details.RetryAfterSeconds > 0 {
591 return int(t.Status().Details.RetryAfterSeconds), true
598 // ReasonForError returns the HTTP status for a particular error.
599 func ReasonForError(err error) metav1.StatusReason {
600 switch t := err.(type) {
602 return t.Status().Reason
604 return metav1.StatusReasonUnknown