blob: afd97f7adcb908bf77f4eb79011af5420ede6db6 [file] [log] [blame]
sslobodrd046be82019-01-16 10:02:22 -05001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package errors
18
19import (
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "reflect"
24 "strings"
25
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"
30)
31
32const (
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
36)
37
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.
40type StatusError struct {
41 ErrStatus metav1.Status
42}
43
44// APIStatus is exposed by errors that can be converted to an api.Status object
45// for finer grained details.
46type APIStatus interface {
47 Status() metav1.Status
48}
49
50var _ error = &StatusError{}
51
52// Error implements the Error interface.
53func (e *StatusError) Error() string {
54 return e.ErrStatus.Message
55}
56
57// Status allows access to e's status without having to know the detailed workings
58// of StatusError.
59func (e *StatusError) Status() metav1.Status {
60 return e.ErrStatus
61}
62
63// DebugError reports extended info about the error to debug output.
64func (e *StatusError) DebugError() (string, []interface{}) {
65 if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil {
66 return "server response object: %s", []interface{}{string(out)}
67 }
68 return "server response object: %#v", []interface{}{e.ErrStatus}
69}
70
71// UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
72type UnexpectedObjectError struct {
73 Object runtime.Object
74}
75
76// Error returns an error message describing 'u'.
77func (u *UnexpectedObjectError) Error() string {
78 return fmt.Sprintf("unexpected object: %v", u.Object)
79}
80
81// FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise,
82// returns an UnexpecteObjectError.
83func FromObject(obj runtime.Object) error {
84 switch t := obj.(type) {
85 case *metav1.Status:
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") {
91 break
92 }
93 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(t.UnstructuredContent(), &status); err != nil {
94 return err
95 }
96 if status.APIVersion != "v1" && status.APIVersion != "meta.k8s.io/v1" {
97 break
98 }
99 return &StatusError{ErrStatus: status}
100 }
101 return &UnexpectedObjectError{obj}
102}
103
104// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
105func 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,
113 Name: name,
114 },
115 Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name),
116 }}
117}
118
119// NewAlreadyExists returns an error indicating the item requested exists by that identifier.
120func 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,
128 Name: name,
129 },
130 Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name),
131 }}
132}
133
134// NewUnauthorized returns an error indicating the client is not authorized to perform the requested
135// action.
136func NewUnauthorized(reason string) *StatusError {
137 message := reason
138 if len(message) == 0 {
139 message = "not authorized"
140 }
141 return &StatusError{metav1.Status{
142 Status: metav1.StatusFailure,
143 Code: http.StatusUnauthorized,
144 Reason: metav1.StatusReasonUnauthorized,
145 Message: message,
146 }}
147}
148
149// NewForbidden returns an error indicating the requested action was forbidden
150func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
151 var message string
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)
156 } else {
157 message = fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err)
158 }
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,
166 Name: name,
167 },
168 Message: message,
169 }}
170}
171
172// NewConflict returns an error indicating the item can't be updated as provided.
173func 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,
181 Name: name,
182 },
183 Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err),
184 }}
185}
186
Stephane Barbarie260a5632019-02-26 16:12:49 -0500187// NewApplyConflict returns an error including details on the requests apply conflicts
188func NewApplyConflict(causes []metav1.StatusCause, message string) *StatusError {
189 return &StatusError{ErrStatus: metav1.Status{
190 Status: metav1.StatusFailure,
191 Code: http.StatusConflict,
192 Reason: metav1.StatusReasonConflict,
193 Details: &metav1.StatusDetails{
194 // TODO: Get obj details here?
195 Causes: causes,
196 },
197 Message: message,
198 }}
199}
200
sslobodrd046be82019-01-16 10:02:22 -0500201// NewGone returns an error indicating the item no longer available at the server and no forwarding address is known.
202func NewGone(message string) *StatusError {
203 return &StatusError{metav1.Status{
204 Status: metav1.StatusFailure,
205 Code: http.StatusGone,
206 Reason: metav1.StatusReasonGone,
207 Message: message,
208 }}
209}
210
211// NewResourceExpired creates an error that indicates that the requested resource content has expired from
212// the server (usually due to a resourceVersion that is too old).
213func NewResourceExpired(message string) *StatusError {
214 return &StatusError{metav1.Status{
215 Status: metav1.StatusFailure,
216 Code: http.StatusGone,
217 Reason: metav1.StatusReasonExpired,
218 Message: message,
219 }}
220}
221
222// NewInvalid returns an error indicating the item is invalid and cannot be processed.
223func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError {
224 causes := make([]metav1.StatusCause, 0, len(errs))
225 for i := range errs {
226 err := errs[i]
227 causes = append(causes, metav1.StatusCause{
228 Type: metav1.CauseType(err.Type),
229 Message: err.ErrorBody(),
230 Field: err.Field,
231 })
232 }
233 return &StatusError{metav1.Status{
234 Status: metav1.StatusFailure,
235 Code: http.StatusUnprocessableEntity,
236 Reason: metav1.StatusReasonInvalid,
237 Details: &metav1.StatusDetails{
238 Group: qualifiedKind.Group,
239 Kind: qualifiedKind.Kind,
240 Name: name,
241 Causes: causes,
242 },
243 Message: fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, errs.ToAggregate()),
244 }}
245}
246
247// NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
248func NewBadRequest(reason string) *StatusError {
249 return &StatusError{metav1.Status{
250 Status: metav1.StatusFailure,
251 Code: http.StatusBadRequest,
252 Reason: metav1.StatusReasonBadRequest,
253 Message: reason,
254 }}
255}
256
257// NewTooManyRequests creates an error that indicates that the client must try again later because
258// the specified endpoint is not accepting requests. More specific details should be provided
259// if client should know why the failure was limited4.
260func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError {
261 return &StatusError{metav1.Status{
262 Status: metav1.StatusFailure,
263 Code: http.StatusTooManyRequests,
264 Reason: metav1.StatusReasonTooManyRequests,
265 Message: message,
266 Details: &metav1.StatusDetails{
267 RetryAfterSeconds: int32(retryAfterSeconds),
268 },
269 }}
270}
271
272// NewServiceUnavailable creates an error that indicates that the requested service is unavailable.
273func NewServiceUnavailable(reason string) *StatusError {
274 return &StatusError{metav1.Status{
275 Status: metav1.StatusFailure,
276 Code: http.StatusServiceUnavailable,
277 Reason: metav1.StatusReasonServiceUnavailable,
278 Message: reason,
279 }}
280}
281
282// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
283func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError {
284 return &StatusError{metav1.Status{
285 Status: metav1.StatusFailure,
286 Code: http.StatusMethodNotAllowed,
287 Reason: metav1.StatusReasonMethodNotAllowed,
288 Details: &metav1.StatusDetails{
289 Group: qualifiedResource.Group,
290 Kind: qualifiedResource.Resource,
291 },
292 Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()),
293 }}
294}
295
296// NewServerTimeout returns an error indicating the requested action could not be completed due to a
297// transient error, and the client should try again.
298func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError {
299 return &StatusError{metav1.Status{
300 Status: metav1.StatusFailure,
301 Code: http.StatusInternalServerError,
302 Reason: metav1.StatusReasonServerTimeout,
303 Details: &metav1.StatusDetails{
304 Group: qualifiedResource.Group,
305 Kind: qualifiedResource.Resource,
306 Name: operation,
307 RetryAfterSeconds: int32(retryAfterSeconds),
308 },
309 Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()),
310 }}
311}
312
313// NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we
314// happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this.
315func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError {
316 return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds)
317}
318
319// NewInternalError returns an error indicating the item is invalid and cannot be processed.
320func NewInternalError(err error) *StatusError {
321 return &StatusError{metav1.Status{
322 Status: metav1.StatusFailure,
323 Code: http.StatusInternalServerError,
324 Reason: metav1.StatusReasonInternalError,
325 Details: &metav1.StatusDetails{
326 Causes: []metav1.StatusCause{{Message: err.Error()}},
327 },
328 Message: fmt.Sprintf("Internal error occurred: %v", err),
329 }}
330}
331
332// NewTimeoutError returns an error indicating that a timeout occurred before the request
333// could be completed. Clients may retry, but the operation may still complete.
334func NewTimeoutError(message string, retryAfterSeconds int) *StatusError {
335 return &StatusError{metav1.Status{
336 Status: metav1.StatusFailure,
337 Code: http.StatusGatewayTimeout,
338 Reason: metav1.StatusReasonTimeout,
339 Message: fmt.Sprintf("Timeout: %s", message),
340 Details: &metav1.StatusDetails{
341 RetryAfterSeconds: int32(retryAfterSeconds),
342 },
343 }}
344}
345
346// NewTooManyRequestsError returns an error indicating that the request was rejected because
347// the server has received too many requests. Client should wait and retry. But if the request
348// is perishable, then the client should not retry the request.
349func NewTooManyRequestsError(message string) *StatusError {
350 return &StatusError{metav1.Status{
351 Status: metav1.StatusFailure,
352 Code: StatusTooManyRequests,
353 Reason: metav1.StatusReasonTooManyRequests,
354 Message: fmt.Sprintf("Too many requests: %s", message),
355 }}
356}
357
Stephane Barbarie260a5632019-02-26 16:12:49 -0500358// NewRequestEntityTooLargeError returns an error indicating that the request
359// entity was too large.
360func NewRequestEntityTooLargeError(message string) *StatusError {
361 return &StatusError{metav1.Status{
362 Status: metav1.StatusFailure,
363 Code: http.StatusRequestEntityTooLarge,
364 Reason: metav1.StatusReasonRequestEntityTooLarge,
365 Message: fmt.Sprintf("Request entity too large: %s", message),
366 }}
367}
368
sslobodrd046be82019-01-16 10:02:22 -0500369// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
370func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
371 reason := metav1.StatusReasonUnknown
372 message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
373 switch code {
374 case http.StatusConflict:
375 if verb == "POST" {
376 reason = metav1.StatusReasonAlreadyExists
377 } else {
378 reason = metav1.StatusReasonConflict
379 }
380 message = "the server reported a conflict"
381 case http.StatusNotFound:
382 reason = metav1.StatusReasonNotFound
383 message = "the server could not find the requested resource"
384 case http.StatusBadRequest:
385 reason = metav1.StatusReasonBadRequest
386 message = "the server rejected our request for an unknown reason"
387 case http.StatusUnauthorized:
388 reason = metav1.StatusReasonUnauthorized
389 message = "the server has asked for the client to provide credentials"
390 case http.StatusForbidden:
391 reason = metav1.StatusReasonForbidden
392 // the server message has details about who is trying to perform what action. Keep its message.
393 message = serverMessage
394 case http.StatusNotAcceptable:
395 reason = metav1.StatusReasonNotAcceptable
396 // the server message has details about what types are acceptable
397 message = serverMessage
398 case http.StatusUnsupportedMediaType:
399 reason = metav1.StatusReasonUnsupportedMediaType
400 // the server message has details about what types are acceptable
401 message = serverMessage
402 case http.StatusMethodNotAllowed:
403 reason = metav1.StatusReasonMethodNotAllowed
404 message = "the server does not allow this method on the requested resource"
405 case http.StatusUnprocessableEntity:
406 reason = metav1.StatusReasonInvalid
407 message = "the server rejected our request due to an error in our request"
408 case http.StatusServiceUnavailable:
409 reason = metav1.StatusReasonServiceUnavailable
410 message = "the server is currently unable to handle the request"
411 case http.StatusGatewayTimeout:
412 reason = metav1.StatusReasonTimeout
413 message = "the server was unable to return a response in the time allotted, but may still be processing the request"
414 case http.StatusTooManyRequests:
415 reason = metav1.StatusReasonTooManyRequests
416 message = "the server has received too many requests and has asked us to try again later"
417 default:
418 if code >= 500 {
419 reason = metav1.StatusReasonInternalError
420 message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage)
421 }
422 }
423 switch {
424 case !qualifiedResource.Empty() && len(name) > 0:
425 message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name)
426 case !qualifiedResource.Empty():
427 message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String())
428 }
429 var causes []metav1.StatusCause
430 if isUnexpectedResponse {
431 causes = []metav1.StatusCause{
432 {
433 Type: metav1.CauseTypeUnexpectedServerResponse,
434 Message: serverMessage,
435 },
436 }
437 } else {
438 causes = nil
439 }
440 return &StatusError{metav1.Status{
441 Status: metav1.StatusFailure,
442 Code: int32(code),
443 Reason: reason,
444 Details: &metav1.StatusDetails{
445 Group: qualifiedResource.Group,
446 Kind: qualifiedResource.Resource,
447 Name: name,
448
449 Causes: causes,
450 RetryAfterSeconds: int32(retryAfterSeconds),
451 },
452 Message: message,
453 }}
454}
455
456// IsNotFound returns true if the specified error was created by NewNotFound.
457func IsNotFound(err error) bool {
458 return ReasonForError(err) == metav1.StatusReasonNotFound
459}
460
461// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
462func IsAlreadyExists(err error) bool {
463 return ReasonForError(err) == metav1.StatusReasonAlreadyExists
464}
465
466// IsConflict determines if the err is an error which indicates the provided update conflicts.
467func IsConflict(err error) bool {
468 return ReasonForError(err) == metav1.StatusReasonConflict
469}
470
471// IsInvalid determines if the err is an error which indicates the provided resource is not valid.
472func IsInvalid(err error) bool {
473 return ReasonForError(err) == metav1.StatusReasonInvalid
474}
475
476// IsGone is true if the error indicates the requested resource is no longer available.
477func IsGone(err error) bool {
478 return ReasonForError(err) == metav1.StatusReasonGone
479}
480
481// IsResourceExpired is true if the error indicates the resource has expired and the current action is
482// no longer possible.
483func IsResourceExpired(err error) bool {
484 return ReasonForError(err) == metav1.StatusReasonExpired
485}
486
487// IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header
488func IsNotAcceptable(err error) bool {
489 return ReasonForError(err) == metav1.StatusReasonNotAcceptable
490}
491
492// IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header
493func IsUnsupportedMediaType(err error) bool {
494 return ReasonForError(err) == metav1.StatusReasonUnsupportedMediaType
495}
496
497// IsMethodNotSupported determines if the err is an error which indicates the provided action could not
498// be performed because it is not supported by the server.
499func IsMethodNotSupported(err error) bool {
500 return ReasonForError(err) == metav1.StatusReasonMethodNotAllowed
501}
502
503// IsServiceUnavailable is true if the error indicates the underlying service is no longer available.
504func IsServiceUnavailable(err error) bool {
505 return ReasonForError(err) == metav1.StatusReasonServiceUnavailable
506}
507
508// IsBadRequest determines if err is an error which indicates that the request is invalid.
509func IsBadRequest(err error) bool {
510 return ReasonForError(err) == metav1.StatusReasonBadRequest
511}
512
513// IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
514// requires authentication by the user.
515func IsUnauthorized(err error) bool {
516 return ReasonForError(err) == metav1.StatusReasonUnauthorized
517}
518
519// IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
520// be completed as requested.
521func IsForbidden(err error) bool {
522 return ReasonForError(err) == metav1.StatusReasonForbidden
523}
524
525// IsTimeout determines if err is an error which indicates that request times out due to long
526// processing.
527func IsTimeout(err error) bool {
528 return ReasonForError(err) == metav1.StatusReasonTimeout
529}
530
531// IsServerTimeout determines if err is an error which indicates that the request needs to be retried
532// by the client.
533func IsServerTimeout(err error) bool {
534 return ReasonForError(err) == metav1.StatusReasonServerTimeout
535}
536
537// IsInternalError determines if err is an error which indicates an internal server error.
538func IsInternalError(err error) bool {
539 return ReasonForError(err) == metav1.StatusReasonInternalError
540}
541
542// IsTooManyRequests determines if err is an error which indicates that there are too many requests
543// that the server cannot handle.
544func IsTooManyRequests(err error) bool {
545 if ReasonForError(err) == metav1.StatusReasonTooManyRequests {
546 return true
547 }
548 switch t := err.(type) {
549 case APIStatus:
550 return t.Status().Code == http.StatusTooManyRequests
551 }
552 return false
553}
554
Stephane Barbarie260a5632019-02-26 16:12:49 -0500555// IsRequestEntityTooLargeError determines if err is an error which indicates
556// the request entity is too large.
557func IsRequestEntityTooLargeError(err error) bool {
558 if ReasonForError(err) == metav1.StatusReasonRequestEntityTooLarge {
559 return true
560 }
561 switch t := err.(type) {
562 case APIStatus:
563 return t.Status().Code == http.StatusRequestEntityTooLarge
564 }
565 return false
566}
567
sslobodrd046be82019-01-16 10:02:22 -0500568// IsUnexpectedServerError returns true if the server response was not in the expected API format,
569// and may be the result of another HTTP actor.
570func IsUnexpectedServerError(err error) bool {
571 switch t := err.(type) {
572 case APIStatus:
573 if d := t.Status().Details; d != nil {
574 for _, cause := range d.Causes {
575 if cause.Type == metav1.CauseTypeUnexpectedServerResponse {
576 return true
577 }
578 }
579 }
580 }
581 return false
582}
583
584// IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
585func IsUnexpectedObjectError(err error) bool {
586 _, ok := err.(*UnexpectedObjectError)
587 return err != nil && ok
588}
589
590// SuggestsClientDelay returns true if this error suggests a client delay as well as the
591// suggested seconds to wait, or false if the error does not imply a wait. It does not
592// address whether the error *should* be retried, since some errors (like a 3xx) may
593// request delay without retry.
594func SuggestsClientDelay(err error) (int, bool) {
595 switch t := err.(type) {
596 case APIStatus:
597 if t.Status().Details != nil {
598 switch t.Status().Reason {
599 // this StatusReason explicitly requests the caller to delay the action
600 case metav1.StatusReasonServerTimeout:
601 return int(t.Status().Details.RetryAfterSeconds), true
602 }
603 // If the client requests that we retry after a certain number of seconds
604 if t.Status().Details.RetryAfterSeconds > 0 {
605 return int(t.Status().Details.RetryAfterSeconds), true
606 }
607 }
608 }
609 return 0, false
610}
611
612// ReasonForError returns the HTTP status for a particular error.
613func ReasonForError(err error) metav1.StatusReason {
614 switch t := err.(type) {
615 case APIStatus:
616 return t.Status().Reason
617 }
618 return metav1.StatusReasonUnknown
619}