blob: e736a9861400c5b643c478232969739187436054 [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
187// NewGone returns an error indicating the item no longer available at the server and no forwarding address is known.
188func NewGone(message string) *StatusError {
189 return &StatusError{metav1.Status{
190 Status: metav1.StatusFailure,
191 Code: http.StatusGone,
192 Reason: metav1.StatusReasonGone,
193 Message: message,
194 }}
195}
196
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).
199func NewResourceExpired(message string) *StatusError {
200 return &StatusError{metav1.Status{
201 Status: metav1.StatusFailure,
202 Code: http.StatusGone,
203 Reason: metav1.StatusReasonExpired,
204 Message: message,
205 }}
206}
207
208// NewInvalid returns an error indicating the item is invalid and cannot be processed.
209func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError {
210 causes := make([]metav1.StatusCause, 0, len(errs))
211 for i := range errs {
212 err := errs[i]
213 causes = append(causes, metav1.StatusCause{
214 Type: metav1.CauseType(err.Type),
215 Message: err.ErrorBody(),
216 Field: err.Field,
217 })
218 }
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,
226 Name: name,
227 Causes: causes,
228 },
229 Message: fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, errs.ToAggregate()),
230 }}
231}
232
233// NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
234func NewBadRequest(reason string) *StatusError {
235 return &StatusError{metav1.Status{
236 Status: metav1.StatusFailure,
237 Code: http.StatusBadRequest,
238 Reason: metav1.StatusReasonBadRequest,
239 Message: reason,
240 }}
241}
242
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.
246func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError {
247 return &StatusError{metav1.Status{
248 Status: metav1.StatusFailure,
249 Code: http.StatusTooManyRequests,
250 Reason: metav1.StatusReasonTooManyRequests,
251 Message: message,
252 Details: &metav1.StatusDetails{
253 RetryAfterSeconds: int32(retryAfterSeconds),
254 },
255 }}
256}
257
258// NewServiceUnavailable creates an error that indicates that the requested service is unavailable.
259func NewServiceUnavailable(reason string) *StatusError {
260 return &StatusError{metav1.Status{
261 Status: metav1.StatusFailure,
262 Code: http.StatusServiceUnavailable,
263 Reason: metav1.StatusReasonServiceUnavailable,
264 Message: reason,
265 }}
266}
267
268// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
269func 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,
277 },
278 Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()),
279 }}
280}
281
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.
284func 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,
292 Name: operation,
293 RetryAfterSeconds: int32(retryAfterSeconds),
294 },
295 Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()),
296 }}
297}
298
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.
301func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError {
302 return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds)
303}
304
305// NewInternalError returns an error indicating the item is invalid and cannot be processed.
306func 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()}},
313 },
314 Message: fmt.Sprintf("Internal error occurred: %v", err),
315 }}
316}
317
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.
320func 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),
328 },
329 }}
330}
331
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.
335func 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),
341 }}
342}
343
344// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
345func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
346 reason := metav1.StatusReasonUnknown
347 message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
348 switch code {
349 case http.StatusConflict:
350 if verb == "POST" {
351 reason = metav1.StatusReasonAlreadyExists
352 } else {
353 reason = metav1.StatusReasonConflict
354 }
355 message = "the server reported a conflict"
356 case http.StatusNotFound:
357 reason = metav1.StatusReasonNotFound
358 message = "the server could not find the requested resource"
359 case http.StatusBadRequest:
360 reason = metav1.StatusReasonBadRequest
361 message = "the server rejected our request for an unknown reason"
362 case http.StatusUnauthorized:
363 reason = metav1.StatusReasonUnauthorized
364 message = "the server has asked for the client to provide credentials"
365 case http.StatusForbidden:
366 reason = metav1.StatusReasonForbidden
367 // the server message has details about who is trying to perform what action. Keep its message.
368 message = serverMessage
369 case http.StatusNotAcceptable:
370 reason = metav1.StatusReasonNotAcceptable
371 // the server message has details about what types are acceptable
372 message = serverMessage
373 case http.StatusUnsupportedMediaType:
374 reason = metav1.StatusReasonUnsupportedMediaType
375 // the server message has details about what types are acceptable
376 message = serverMessage
377 case http.StatusMethodNotAllowed:
378 reason = metav1.StatusReasonMethodNotAllowed
379 message = "the server does not allow this method on the requested resource"
380 case http.StatusUnprocessableEntity:
381 reason = metav1.StatusReasonInvalid
382 message = "the server rejected our request due to an error in our request"
383 case http.StatusServiceUnavailable:
384 reason = metav1.StatusReasonServiceUnavailable
385 message = "the server is currently unable to handle the request"
386 case http.StatusGatewayTimeout:
387 reason = metav1.StatusReasonTimeout
388 message = "the server was unable to return a response in the time allotted, but may still be processing the request"
389 case http.StatusTooManyRequests:
390 reason = metav1.StatusReasonTooManyRequests
391 message = "the server has received too many requests and has asked us to try again later"
392 default:
393 if code >= 500 {
394 reason = metav1.StatusReasonInternalError
395 message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage)
396 }
397 }
398 switch {
399 case !qualifiedResource.Empty() && len(name) > 0:
400 message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name)
401 case !qualifiedResource.Empty():
402 message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String())
403 }
404 var causes []metav1.StatusCause
405 if isUnexpectedResponse {
406 causes = []metav1.StatusCause{
407 {
408 Type: metav1.CauseTypeUnexpectedServerResponse,
409 Message: serverMessage,
410 },
411 }
412 } else {
413 causes = nil
414 }
415 return &StatusError{metav1.Status{
416 Status: metav1.StatusFailure,
417 Code: int32(code),
418 Reason: reason,
419 Details: &metav1.StatusDetails{
420 Group: qualifiedResource.Group,
421 Kind: qualifiedResource.Resource,
422 Name: name,
423
424 Causes: causes,
425 RetryAfterSeconds: int32(retryAfterSeconds),
426 },
427 Message: message,
428 }}
429}
430
431// IsNotFound returns true if the specified error was created by NewNotFound.
432func IsNotFound(err error) bool {
433 return ReasonForError(err) == metav1.StatusReasonNotFound
434}
435
436// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
437func IsAlreadyExists(err error) bool {
438 return ReasonForError(err) == metav1.StatusReasonAlreadyExists
439}
440
441// IsConflict determines if the err is an error which indicates the provided update conflicts.
442func IsConflict(err error) bool {
443 return ReasonForError(err) == metav1.StatusReasonConflict
444}
445
446// IsInvalid determines if the err is an error which indicates the provided resource is not valid.
447func IsInvalid(err error) bool {
448 return ReasonForError(err) == metav1.StatusReasonInvalid
449}
450
451// IsGone is true if the error indicates the requested resource is no longer available.
452func IsGone(err error) bool {
453 return ReasonForError(err) == metav1.StatusReasonGone
454}
455
456// IsResourceExpired is true if the error indicates the resource has expired and the current action is
457// no longer possible.
458func IsResourceExpired(err error) bool {
459 return ReasonForError(err) == metav1.StatusReasonExpired
460}
461
462// IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header
463func IsNotAcceptable(err error) bool {
464 return ReasonForError(err) == metav1.StatusReasonNotAcceptable
465}
466
467// IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header
468func IsUnsupportedMediaType(err error) bool {
469 return ReasonForError(err) == metav1.StatusReasonUnsupportedMediaType
470}
471
472// IsMethodNotSupported determines if the err is an error which indicates the provided action could not
473// be performed because it is not supported by the server.
474func IsMethodNotSupported(err error) bool {
475 return ReasonForError(err) == metav1.StatusReasonMethodNotAllowed
476}
477
478// IsServiceUnavailable is true if the error indicates the underlying service is no longer available.
479func IsServiceUnavailable(err error) bool {
480 return ReasonForError(err) == metav1.StatusReasonServiceUnavailable
481}
482
483// IsBadRequest determines if err is an error which indicates that the request is invalid.
484func IsBadRequest(err error) bool {
485 return ReasonForError(err) == metav1.StatusReasonBadRequest
486}
487
488// IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
489// requires authentication by the user.
490func IsUnauthorized(err error) bool {
491 return ReasonForError(err) == metav1.StatusReasonUnauthorized
492}
493
494// IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
495// be completed as requested.
496func IsForbidden(err error) bool {
497 return ReasonForError(err) == metav1.StatusReasonForbidden
498}
499
500// IsTimeout determines if err is an error which indicates that request times out due to long
501// processing.
502func IsTimeout(err error) bool {
503 return ReasonForError(err) == metav1.StatusReasonTimeout
504}
505
506// IsServerTimeout determines if err is an error which indicates that the request needs to be retried
507// by the client.
508func IsServerTimeout(err error) bool {
509 return ReasonForError(err) == metav1.StatusReasonServerTimeout
510}
511
512// IsInternalError determines if err is an error which indicates an internal server error.
513func IsInternalError(err error) bool {
514 return ReasonForError(err) == metav1.StatusReasonInternalError
515}
516
517// IsTooManyRequests determines if err is an error which indicates that there are too many requests
518// that the server cannot handle.
519func IsTooManyRequests(err error) bool {
520 if ReasonForError(err) == metav1.StatusReasonTooManyRequests {
521 return true
522 }
523 switch t := err.(type) {
524 case APIStatus:
525 return t.Status().Code == http.StatusTooManyRequests
526 }
527 return false
528}
529
530// IsUnexpectedServerError returns true if the server response was not in the expected API format,
531// and may be the result of another HTTP actor.
532func IsUnexpectedServerError(err error) bool {
533 switch t := err.(type) {
534 case APIStatus:
535 if d := t.Status().Details; d != nil {
536 for _, cause := range d.Causes {
537 if cause.Type == metav1.CauseTypeUnexpectedServerResponse {
538 return true
539 }
540 }
541 }
542 }
543 return false
544}
545
546// IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
547func IsUnexpectedObjectError(err error) bool {
548 _, ok := err.(*UnexpectedObjectError)
549 return err != nil && ok
550}
551
552// SuggestsClientDelay returns true if this error suggests a client delay as well as the
553// suggested seconds to wait, or false if the error does not imply a wait. It does not
554// address whether the error *should* be retried, since some errors (like a 3xx) may
555// request delay without retry.
556func SuggestsClientDelay(err error) (int, bool) {
557 switch t := err.(type) {
558 case APIStatus:
559 if t.Status().Details != nil {
560 switch t.Status().Reason {
561 // this StatusReason explicitly requests the caller to delay the action
562 case metav1.StatusReasonServerTimeout:
563 return int(t.Status().Details.RetryAfterSeconds), true
564 }
565 // If the client requests that we retry after a certain number of seconds
566 if t.Status().Details.RetryAfterSeconds > 0 {
567 return int(t.Status().Details.RetryAfterSeconds), true
568 }
569 }
570 }
571 return 0, false
572}
573
574// ReasonForError returns the HTTP status for a particular error.
575func ReasonForError(err error) metav1.StatusReason {
576 switch t := err.(type) {
577 case APIStatus:
578 return t.Status().Reason
579 }
580 return metav1.StatusReasonUnknown
581}