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