errors.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package errors
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "net/http"
  18. "strings"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/runtime"
  21. "k8s.io/apimachinery/pkg/runtime/schema"
  22. "k8s.io/apimachinery/pkg/util/validation/field"
  23. )
  24. // HTTP Status codes not in the golang http package.
  25. const (
  26. StatusUnprocessableEntity = 422
  27. StatusTooManyRequests = 429
  28. // StatusServerTimeout is an indication that a transient server error has
  29. // occurred and the client *should* retry, with an optional Retry-After
  30. // header to specify the back off window.
  31. StatusServerTimeout = 504
  32. )
  33. // StatusError is an error intended for consumption by a REST API server; it can also be
  34. // reconstructed by clients from a REST response. Public to allow easy type switches.
  35. type StatusError struct {
  36. ErrStatus metav1.Status
  37. }
  38. // APIStatus is exposed by errors that can be converted to an api.Status object
  39. // for finer grained details.
  40. type APIStatus interface {
  41. Status() metav1.Status
  42. }
  43. var _ error = &StatusError{}
  44. // Error implements the Error interface.
  45. func (e *StatusError) Error() string {
  46. return e.ErrStatus.Message
  47. }
  48. // Status allows access to e's status without having to know the detailed workings
  49. // of StatusError.
  50. func (e *StatusError) Status() metav1.Status {
  51. return e.ErrStatus
  52. }
  53. // DebugError reports extended info about the error to debug output.
  54. func (e *StatusError) DebugError() (string, []interface{}) {
  55. if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil {
  56. return "server response object: %s", []interface{}{string(out)}
  57. }
  58. return "server response object: %#v", []interface{}{e.ErrStatus}
  59. }
  60. // UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
  61. type UnexpectedObjectError struct {
  62. Object runtime.Object
  63. }
  64. // Error returns an error message describing 'u'.
  65. func (u *UnexpectedObjectError) Error() string {
  66. return fmt.Sprintf("unexpected object: %v", u.Object)
  67. }
  68. // FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise,
  69. // returns an UnexpecteObjectError.
  70. func FromObject(obj runtime.Object) error {
  71. switch t := obj.(type) {
  72. case *metav1.Status:
  73. return &StatusError{*t}
  74. }
  75. return &UnexpectedObjectError{obj}
  76. }
  77. // NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
  78. func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError {
  79. return &StatusError{metav1.Status{
  80. Status: metav1.StatusFailure,
  81. Code: http.StatusNotFound,
  82. Reason: metav1.StatusReasonNotFound,
  83. Details: &metav1.StatusDetails{
  84. Group: qualifiedResource.Group,
  85. Kind: qualifiedResource.Resource,
  86. Name: name,
  87. },
  88. Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name),
  89. }}
  90. }
  91. // NewAlreadyExists returns an error indicating the item requested exists by that identifier.
  92. func NewAlreadyExists(qualifiedResource schema.GroupResource, name string) *StatusError {
  93. return &StatusError{metav1.Status{
  94. Status: metav1.StatusFailure,
  95. Code: http.StatusConflict,
  96. Reason: metav1.StatusReasonAlreadyExists,
  97. Details: &metav1.StatusDetails{
  98. Group: qualifiedResource.Group,
  99. Kind: qualifiedResource.Resource,
  100. Name: name,
  101. },
  102. Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name),
  103. }}
  104. }
  105. // NewUnauthorized returns an error indicating the client is not authorized to perform the requested
  106. // action.
  107. func NewUnauthorized(reason string) *StatusError {
  108. message := reason
  109. if len(message) == 0 {
  110. message = "not authorized"
  111. }
  112. return &StatusError{metav1.Status{
  113. Status: metav1.StatusFailure,
  114. Code: http.StatusUnauthorized,
  115. Reason: metav1.StatusReasonUnauthorized,
  116. Message: message,
  117. }}
  118. }
  119. // NewForbidden returns an error indicating the requested action was forbidden
  120. func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
  121. return &StatusError{metav1.Status{
  122. Status: metav1.StatusFailure,
  123. Code: http.StatusForbidden,
  124. Reason: metav1.StatusReasonForbidden,
  125. Details: &metav1.StatusDetails{
  126. Group: qualifiedResource.Group,
  127. Kind: qualifiedResource.Resource,
  128. Name: name,
  129. },
  130. Message: fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err),
  131. }}
  132. }
  133. // NewConflict returns an error indicating the item can't be updated as provided.
  134. func NewConflict(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
  135. return &StatusError{metav1.Status{
  136. Status: metav1.StatusFailure,
  137. Code: http.StatusConflict,
  138. Reason: metav1.StatusReasonConflict,
  139. Details: &metav1.StatusDetails{
  140. Group: qualifiedResource.Group,
  141. Kind: qualifiedResource.Resource,
  142. Name: name,
  143. },
  144. Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err),
  145. }}
  146. }
  147. // NewGone returns an error indicating the item no longer available at the server and no forwarding address is known.
  148. func NewGone(message string) *StatusError {
  149. return &StatusError{metav1.Status{
  150. Status: metav1.StatusFailure,
  151. Code: http.StatusGone,
  152. Reason: metav1.StatusReasonGone,
  153. Message: message,
  154. }}
  155. }
  156. // NewInvalid returns an error indicating the item is invalid and cannot be processed.
  157. func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError {
  158. causes := make([]metav1.StatusCause, 0, len(errs))
  159. for i := range errs {
  160. err := errs[i]
  161. causes = append(causes, metav1.StatusCause{
  162. Type: metav1.CauseType(err.Type),
  163. Message: err.ErrorBody(),
  164. Field: err.Field,
  165. })
  166. }
  167. return &StatusError{metav1.Status{
  168. Status: metav1.StatusFailure,
  169. Code: StatusUnprocessableEntity, // RFC 4918: StatusUnprocessableEntity
  170. Reason: metav1.StatusReasonInvalid,
  171. Details: &metav1.StatusDetails{
  172. Group: qualifiedKind.Group,
  173. Kind: qualifiedKind.Kind,
  174. Name: name,
  175. Causes: causes,
  176. },
  177. Message: fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, errs.ToAggregate()),
  178. }}
  179. }
  180. // NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
  181. func NewBadRequest(reason string) *StatusError {
  182. return &StatusError{metav1.Status{
  183. Status: metav1.StatusFailure,
  184. Code: http.StatusBadRequest,
  185. Reason: metav1.StatusReasonBadRequest,
  186. Message: reason,
  187. }}
  188. }
  189. // NewServiceUnavailable creates an error that indicates that the requested service is unavailable.
  190. func NewServiceUnavailable(reason string) *StatusError {
  191. return &StatusError{metav1.Status{
  192. Status: metav1.StatusFailure,
  193. Code: http.StatusServiceUnavailable,
  194. Reason: metav1.StatusReasonServiceUnavailable,
  195. Message: reason,
  196. }}
  197. }
  198. // NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
  199. func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError {
  200. return &StatusError{metav1.Status{
  201. Status: metav1.StatusFailure,
  202. Code: http.StatusMethodNotAllowed,
  203. Reason: metav1.StatusReasonMethodNotAllowed,
  204. Details: &metav1.StatusDetails{
  205. Group: qualifiedResource.Group,
  206. Kind: qualifiedResource.Resource,
  207. },
  208. Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()),
  209. }}
  210. }
  211. // NewServerTimeout returns an error indicating the requested action could not be completed due to a
  212. // transient error, and the client should try again.
  213. func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError {
  214. return &StatusError{metav1.Status{
  215. Status: metav1.StatusFailure,
  216. Code: http.StatusInternalServerError,
  217. Reason: metav1.StatusReasonServerTimeout,
  218. Details: &metav1.StatusDetails{
  219. Group: qualifiedResource.Group,
  220. Kind: qualifiedResource.Resource,
  221. Name: operation,
  222. RetryAfterSeconds: int32(retryAfterSeconds),
  223. },
  224. Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()),
  225. }}
  226. }
  227. // NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we
  228. // happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this.
  229. func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError {
  230. return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds)
  231. }
  232. // NewInternalError returns an error indicating the item is invalid and cannot be processed.
  233. func NewInternalError(err error) *StatusError {
  234. return &StatusError{metav1.Status{
  235. Status: metav1.StatusFailure,
  236. Code: http.StatusInternalServerError,
  237. Reason: metav1.StatusReasonInternalError,
  238. Details: &metav1.StatusDetails{
  239. Causes: []metav1.StatusCause{{Message: err.Error()}},
  240. },
  241. Message: fmt.Sprintf("Internal error occurred: %v", err),
  242. }}
  243. }
  244. // NewTimeoutError returns an error indicating that a timeout occurred before the request
  245. // could be completed. Clients may retry, but the operation may still complete.
  246. func NewTimeoutError(message string, retryAfterSeconds int) *StatusError {
  247. return &StatusError{metav1.Status{
  248. Status: metav1.StatusFailure,
  249. Code: StatusServerTimeout,
  250. Reason: metav1.StatusReasonTimeout,
  251. Message: fmt.Sprintf("Timeout: %s", message),
  252. Details: &metav1.StatusDetails{
  253. RetryAfterSeconds: int32(retryAfterSeconds),
  254. },
  255. }}
  256. }
  257. // NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
  258. func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
  259. reason := metav1.StatusReasonUnknown
  260. message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
  261. switch code {
  262. case http.StatusConflict:
  263. if verb == "POST" {
  264. reason = metav1.StatusReasonAlreadyExists
  265. } else {
  266. reason = metav1.StatusReasonConflict
  267. }
  268. message = "the server reported a conflict"
  269. case http.StatusNotFound:
  270. reason = metav1.StatusReasonNotFound
  271. message = "the server could not find the requested resource"
  272. case http.StatusBadRequest:
  273. reason = metav1.StatusReasonBadRequest
  274. message = "the server rejected our request for an unknown reason"
  275. case http.StatusUnauthorized:
  276. reason = metav1.StatusReasonUnauthorized
  277. message = "the server has asked for the client to provide credentials"
  278. case http.StatusForbidden:
  279. reason = metav1.StatusReasonForbidden
  280. // the server message has details about who is trying to perform what action. Keep its message.
  281. message = serverMessage
  282. case http.StatusMethodNotAllowed:
  283. reason = metav1.StatusReasonMethodNotAllowed
  284. message = "the server does not allow this method on the requested resource"
  285. case StatusUnprocessableEntity:
  286. reason = metav1.StatusReasonInvalid
  287. message = "the server rejected our request due to an error in our request"
  288. case StatusServerTimeout:
  289. reason = metav1.StatusReasonServerTimeout
  290. message = "the server cannot complete the requested operation at this time, try again later"
  291. case StatusTooManyRequests:
  292. reason = metav1.StatusReasonTimeout
  293. message = "the server has received too many requests and has asked us to try again later"
  294. default:
  295. if code >= 500 {
  296. reason = metav1.StatusReasonInternalError
  297. message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage)
  298. }
  299. }
  300. switch {
  301. case !qualifiedResource.Empty() && len(name) > 0:
  302. message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name)
  303. case !qualifiedResource.Empty():
  304. message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String())
  305. }
  306. var causes []metav1.StatusCause
  307. if isUnexpectedResponse {
  308. causes = []metav1.StatusCause{
  309. {
  310. Type: metav1.CauseTypeUnexpectedServerResponse,
  311. Message: serverMessage,
  312. },
  313. }
  314. } else {
  315. causes = nil
  316. }
  317. return &StatusError{metav1.Status{
  318. Status: metav1.StatusFailure,
  319. Code: int32(code),
  320. Reason: reason,
  321. Details: &metav1.StatusDetails{
  322. Group: qualifiedResource.Group,
  323. Kind: qualifiedResource.Resource,
  324. Name: name,
  325. Causes: causes,
  326. RetryAfterSeconds: int32(retryAfterSeconds),
  327. },
  328. Message: message,
  329. }}
  330. }
  331. // IsNotFound returns true if the specified error was created by NewNotFound.
  332. func IsNotFound(err error) bool {
  333. return reasonForError(err) == metav1.StatusReasonNotFound
  334. }
  335. // IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
  336. func IsAlreadyExists(err error) bool {
  337. return reasonForError(err) == metav1.StatusReasonAlreadyExists
  338. }
  339. // IsConflict determines if the err is an error which indicates the provided update conflicts.
  340. func IsConflict(err error) bool {
  341. return reasonForError(err) == metav1.StatusReasonConflict
  342. }
  343. // IsInvalid determines if the err is an error which indicates the provided resource is not valid.
  344. func IsInvalid(err error) bool {
  345. return reasonForError(err) == metav1.StatusReasonInvalid
  346. }
  347. // IsMethodNotSupported determines if the err is an error which indicates the provided action could not
  348. // be performed because it is not supported by the server.
  349. func IsMethodNotSupported(err error) bool {
  350. return reasonForError(err) == metav1.StatusReasonMethodNotAllowed
  351. }
  352. // IsBadRequest determines if err is an error which indicates that the request is invalid.
  353. func IsBadRequest(err error) bool {
  354. return reasonForError(err) == metav1.StatusReasonBadRequest
  355. }
  356. // IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
  357. // requires authentication by the user.
  358. func IsUnauthorized(err error) bool {
  359. return reasonForError(err) == metav1.StatusReasonUnauthorized
  360. }
  361. // IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
  362. // be completed as requested.
  363. func IsForbidden(err error) bool {
  364. return reasonForError(err) == metav1.StatusReasonForbidden
  365. }
  366. // IsTimeout determines if err is an error which indicates that request times out due to long
  367. // processing.
  368. func IsTimeout(err error) bool {
  369. return reasonForError(err) == metav1.StatusReasonTimeout
  370. }
  371. // IsServerTimeout determines if err is an error which indicates that the request needs to be retried
  372. // by the client.
  373. func IsServerTimeout(err error) bool {
  374. return reasonForError(err) == metav1.StatusReasonServerTimeout
  375. }
  376. // IsInternalError determines if err is an error which indicates an internal server error.
  377. func IsInternalError(err error) bool {
  378. return reasonForError(err) == metav1.StatusReasonInternalError
  379. }
  380. // IsTooManyRequests determines if err is an error which indicates that there are too many requests
  381. // that the server cannot handle.
  382. // TODO: update IsTooManyRequests() when the TooManyRequests(429) error returned from the API server has a non-empty Reason field
  383. func IsTooManyRequests(err error) bool {
  384. switch t := err.(type) {
  385. case APIStatus:
  386. return t.Status().Code == StatusTooManyRequests
  387. }
  388. return false
  389. }
  390. // IsUnexpectedServerError returns true if the server response was not in the expected API format,
  391. // and may be the result of another HTTP actor.
  392. func IsUnexpectedServerError(err error) bool {
  393. switch t := err.(type) {
  394. case APIStatus:
  395. if d := t.Status().Details; d != nil {
  396. for _, cause := range d.Causes {
  397. if cause.Type == metav1.CauseTypeUnexpectedServerResponse {
  398. return true
  399. }
  400. }
  401. }
  402. }
  403. return false
  404. }
  405. // IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
  406. func IsUnexpectedObjectError(err error) bool {
  407. _, ok := err.(*UnexpectedObjectError)
  408. return err != nil && ok
  409. }
  410. // SuggestsClientDelay returns true if this error suggests a client delay as well as the
  411. // suggested seconds to wait, or false if the error does not imply a wait.
  412. func SuggestsClientDelay(err error) (int, bool) {
  413. switch t := err.(type) {
  414. case APIStatus:
  415. if t.Status().Details != nil {
  416. switch t.Status().Reason {
  417. case metav1.StatusReasonServerTimeout, metav1.StatusReasonTimeout:
  418. return int(t.Status().Details.RetryAfterSeconds), true
  419. }
  420. }
  421. }
  422. return 0, false
  423. }
  424. func reasonForError(err error) metav1.StatusReason {
  425. switch t := err.(type) {
  426. case APIStatus:
  427. return t.Status().Reason
  428. }
  429. return metav1.StatusReasonUnknown
  430. }