errors.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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. "k8s.io/kubernetes/pkg/api/unversioned"
  20. "k8s.io/kubernetes/pkg/runtime"
  21. "k8s.io/kubernetes/pkg/util/validation/field"
  22. )
  23. // HTTP Status codes not in the golang http package.
  24. const (
  25. StatusUnprocessableEntity = 422
  26. StatusTooManyRequests = 429
  27. // HTTP recommendations are for servers to define 5xx error codes
  28. // for scenarios not covered by behavior. In this case, ServerTimeout
  29. // is an indication that a transient server error has occurred and the
  30. // client *should* retry, with an optional Retry-After header to specify
  31. // the back off window.
  32. StatusServerTimeout = 504
  33. )
  34. // StatusError is an error intended for consumption by a REST API server; it can also be
  35. // reconstructed by clients from a REST response. Public to allow easy type switches.
  36. type StatusError struct {
  37. ErrStatus unversioned.Status
  38. }
  39. // APIStatus is exposed by errors that can be converted to an api.Status object
  40. // for finer grained details.
  41. type APIStatus interface {
  42. Status() unversioned.Status
  43. }
  44. var _ error = &StatusError{}
  45. // Error implements the Error interface.
  46. func (e *StatusError) Error() string {
  47. return e.ErrStatus.Message
  48. }
  49. // Status allows access to e's status without having to know the detailed workings
  50. // of StatusError. Used by pkg/apiserver.
  51. func (e *StatusError) Status() unversioned.Status {
  52. return e.ErrStatus
  53. }
  54. // DebugError reports extended info about the error to debug output.
  55. func (e *StatusError) DebugError() (string, []interface{}) {
  56. if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil {
  57. return "server response object: %s", []interface{}{string(out)}
  58. }
  59. return "server response object: %#v", []interface{}{e.ErrStatus}
  60. }
  61. // UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
  62. type UnexpectedObjectError struct {
  63. Object runtime.Object
  64. }
  65. // Error returns an error message describing 'u'.
  66. func (u *UnexpectedObjectError) Error() string {
  67. return fmt.Sprintf("unexpected object: %v", u.Object)
  68. }
  69. // FromObject generates an StatusError from an unversioned.Status, if that is the type of obj; otherwise,
  70. // returns an UnexpecteObjectError.
  71. func FromObject(obj runtime.Object) error {
  72. switch t := obj.(type) {
  73. case *unversioned.Status:
  74. return &StatusError{*t}
  75. }
  76. return &UnexpectedObjectError{obj}
  77. }
  78. // NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
  79. func NewNotFound(qualifiedResource unversioned.GroupResource, name string) *StatusError {
  80. return &StatusError{unversioned.Status{
  81. Status: unversioned.StatusFailure,
  82. Code: http.StatusNotFound,
  83. Reason: unversioned.StatusReasonNotFound,
  84. Details: &unversioned.StatusDetails{
  85. Group: qualifiedResource.Group,
  86. Kind: qualifiedResource.Resource,
  87. Name: name,
  88. },
  89. Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name),
  90. }}
  91. }
  92. // NewAlreadyExists returns an error indicating the item requested exists by that identifier.
  93. func NewAlreadyExists(qualifiedResource unversioned.GroupResource, name string) *StatusError {
  94. return &StatusError{unversioned.Status{
  95. Status: unversioned.StatusFailure,
  96. Code: http.StatusConflict,
  97. Reason: unversioned.StatusReasonAlreadyExists,
  98. Details: &unversioned.StatusDetails{
  99. Group: qualifiedResource.Group,
  100. Kind: qualifiedResource.Resource,
  101. Name: name,
  102. },
  103. Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name),
  104. }}
  105. }
  106. // NewUnauthorized returns an error indicating the client is not authorized to perform the requested
  107. // action.
  108. func NewUnauthorized(reason string) *StatusError {
  109. message := reason
  110. if len(message) == 0 {
  111. message = "not authorized"
  112. }
  113. return &StatusError{unversioned.Status{
  114. Status: unversioned.StatusFailure,
  115. Code: http.StatusUnauthorized,
  116. Reason: unversioned.StatusReasonUnauthorized,
  117. Message: message,
  118. }}
  119. }
  120. // NewForbidden returns an error indicating the requested action was forbidden
  121. func NewForbidden(qualifiedResource unversioned.GroupResource, name string, err error) *StatusError {
  122. return &StatusError{unversioned.Status{
  123. Status: unversioned.StatusFailure,
  124. Code: http.StatusForbidden,
  125. Reason: unversioned.StatusReasonForbidden,
  126. Details: &unversioned.StatusDetails{
  127. Group: qualifiedResource.Group,
  128. Kind: qualifiedResource.Resource,
  129. Name: name,
  130. },
  131. Message: fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err),
  132. }}
  133. }
  134. // NewConflict returns an error indicating the item can't be updated as provided.
  135. func NewConflict(qualifiedResource unversioned.GroupResource, name string, err error) *StatusError {
  136. return &StatusError{unversioned.Status{
  137. Status: unversioned.StatusFailure,
  138. Code: http.StatusConflict,
  139. Reason: unversioned.StatusReasonConflict,
  140. Details: &unversioned.StatusDetails{
  141. Group: qualifiedResource.Group,
  142. Kind: qualifiedResource.Resource,
  143. Name: name,
  144. },
  145. Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err),
  146. }}
  147. }
  148. // NewGone returns an error indicating the item no longer available at the server and no forwarding address is known.
  149. func NewGone(message string) *StatusError {
  150. return &StatusError{unversioned.Status{
  151. Status: unversioned.StatusFailure,
  152. Code: http.StatusGone,
  153. Reason: unversioned.StatusReasonGone,
  154. Message: message,
  155. }}
  156. }
  157. // NewInvalid returns an error indicating the item is invalid and cannot be processed.
  158. func NewInvalid(qualifiedKind unversioned.GroupKind, name string, errs field.ErrorList) *StatusError {
  159. causes := make([]unversioned.StatusCause, 0, len(errs))
  160. for i := range errs {
  161. err := errs[i]
  162. causes = append(causes, unversioned.StatusCause{
  163. Type: unversioned.CauseType(err.Type),
  164. Message: err.ErrorBody(),
  165. Field: err.Field,
  166. })
  167. }
  168. return &StatusError{unversioned.Status{
  169. Status: unversioned.StatusFailure,
  170. Code: StatusUnprocessableEntity, // RFC 4918: StatusUnprocessableEntity
  171. Reason: unversioned.StatusReasonInvalid,
  172. Details: &unversioned.StatusDetails{
  173. Group: qualifiedKind.Group,
  174. Kind: qualifiedKind.Kind,
  175. Name: name,
  176. Causes: causes,
  177. },
  178. Message: fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, errs.ToAggregate()),
  179. }}
  180. }
  181. // NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
  182. func NewBadRequest(reason string) *StatusError {
  183. return &StatusError{unversioned.Status{
  184. Status: unversioned.StatusFailure,
  185. Code: http.StatusBadRequest,
  186. Reason: unversioned.StatusReasonBadRequest,
  187. Message: reason,
  188. }}
  189. }
  190. // NewServiceUnavailable creates an error that indicates that the requested service is unavailable.
  191. func NewServiceUnavailable(reason string) *StatusError {
  192. return &StatusError{unversioned.Status{
  193. Status: unversioned.StatusFailure,
  194. Code: http.StatusServiceUnavailable,
  195. Reason: unversioned.StatusReasonServiceUnavailable,
  196. Message: reason,
  197. }}
  198. }
  199. // NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
  200. func NewMethodNotSupported(qualifiedResource unversioned.GroupResource, action string) *StatusError {
  201. return &StatusError{unversioned.Status{
  202. Status: unversioned.StatusFailure,
  203. Code: http.StatusMethodNotAllowed,
  204. Reason: unversioned.StatusReasonMethodNotAllowed,
  205. Details: &unversioned.StatusDetails{
  206. Group: qualifiedResource.Group,
  207. Kind: qualifiedResource.Resource,
  208. },
  209. Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()),
  210. }}
  211. }
  212. // NewServerTimeout returns an error indicating the requested action could not be completed due to a
  213. // transient error, and the client should try again.
  214. func NewServerTimeout(qualifiedResource unversioned.GroupResource, operation string, retryAfterSeconds int) *StatusError {
  215. return &StatusError{unversioned.Status{
  216. Status: unversioned.StatusFailure,
  217. Code: http.StatusInternalServerError,
  218. Reason: unversioned.StatusReasonServerTimeout,
  219. Details: &unversioned.StatusDetails{
  220. Group: qualifiedResource.Group,
  221. Kind: qualifiedResource.Resource,
  222. Name: operation,
  223. RetryAfterSeconds: int32(retryAfterSeconds),
  224. },
  225. Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()),
  226. }}
  227. }
  228. // NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we
  229. // happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this.
  230. func NewServerTimeoutForKind(qualifiedKind unversioned.GroupKind, operation string, retryAfterSeconds int) *StatusError {
  231. return NewServerTimeout(unversioned.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds)
  232. }
  233. // NewInternalError returns an error indicating the item is invalid and cannot be processed.
  234. func NewInternalError(err error) *StatusError {
  235. return &StatusError{unversioned.Status{
  236. Status: unversioned.StatusFailure,
  237. Code: http.StatusInternalServerError,
  238. Reason: unversioned.StatusReasonInternalError,
  239. Details: &unversioned.StatusDetails{
  240. Causes: []unversioned.StatusCause{{Message: err.Error()}},
  241. },
  242. Message: fmt.Sprintf("Internal error occurred: %v", err),
  243. }}
  244. }
  245. // NewTimeoutError returns an error indicating that a timeout occurred before the request
  246. // could be completed. Clients may retry, but the operation may still complete.
  247. func NewTimeoutError(message string, retryAfterSeconds int) *StatusError {
  248. return &StatusError{unversioned.Status{
  249. Status: unversioned.StatusFailure,
  250. Code: StatusServerTimeout,
  251. Reason: unversioned.StatusReasonTimeout,
  252. Message: fmt.Sprintf("Timeout: %s", message),
  253. Details: &unversioned.StatusDetails{
  254. RetryAfterSeconds: int32(retryAfterSeconds),
  255. },
  256. }}
  257. }
  258. // NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
  259. func NewGenericServerResponse(code int, verb string, qualifiedResource unversioned.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
  260. reason := unversioned.StatusReasonUnknown
  261. message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
  262. switch code {
  263. case http.StatusConflict:
  264. if verb == "POST" {
  265. reason = unversioned.StatusReasonAlreadyExists
  266. } else {
  267. reason = unversioned.StatusReasonConflict
  268. }
  269. message = "the server reported a conflict"
  270. case http.StatusNotFound:
  271. reason = unversioned.StatusReasonNotFound
  272. message = "the server could not find the requested resource"
  273. case http.StatusBadRequest:
  274. reason = unversioned.StatusReasonBadRequest
  275. message = "the server rejected our request for an unknown reason"
  276. case http.StatusUnauthorized:
  277. reason = unversioned.StatusReasonUnauthorized
  278. message = "the server has asked for the client to provide credentials"
  279. case http.StatusForbidden:
  280. reason = unversioned.StatusReasonForbidden
  281. message = "the server does not allow access to the requested resource"
  282. case http.StatusMethodNotAllowed:
  283. reason = unversioned.StatusReasonMethodNotAllowed
  284. message = "the server does not allow this method on the requested resource"
  285. case StatusUnprocessableEntity:
  286. reason = unversioned.StatusReasonInvalid
  287. message = "the server rejected our request due to an error in our request"
  288. case StatusServerTimeout:
  289. reason = unversioned.StatusReasonServerTimeout
  290. message = "the server cannot complete the requested operation at this time, try again later"
  291. case StatusTooManyRequests:
  292. reason = unversioned.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 = unversioned.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 []unversioned.StatusCause
  307. if isUnexpectedResponse {
  308. causes = []unversioned.StatusCause{
  309. {
  310. Type: unversioned.CauseTypeUnexpectedServerResponse,
  311. Message: serverMessage,
  312. },
  313. }
  314. } else {
  315. causes = nil
  316. }
  317. return &StatusError{unversioned.Status{
  318. Status: unversioned.StatusFailure,
  319. Code: int32(code),
  320. Reason: reason,
  321. Details: &unversioned.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) == unversioned.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) == unversioned.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) == unversioned.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) == unversioned.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) == unversioned.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) == unversioned.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) == unversioned.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) == unversioned.StatusReasonForbidden
  365. }
  366. // IsServerTimeout determines if err is an error which indicates that the request needs to be retried
  367. // by the client.
  368. func IsServerTimeout(err error) bool {
  369. return reasonForError(err) == unversioned.StatusReasonServerTimeout
  370. }
  371. // IsUnexpectedServerError returns true if the server response was not in the expected API format,
  372. // and may be the result of another HTTP actor.
  373. func IsUnexpectedServerError(err error) bool {
  374. switch t := err.(type) {
  375. case APIStatus:
  376. if d := t.Status().Details; d != nil {
  377. for _, cause := range d.Causes {
  378. if cause.Type == unversioned.CauseTypeUnexpectedServerResponse {
  379. return true
  380. }
  381. }
  382. }
  383. }
  384. return false
  385. }
  386. // IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
  387. func IsUnexpectedObjectError(err error) bool {
  388. _, ok := err.(*UnexpectedObjectError)
  389. return err != nil && ok
  390. }
  391. // SuggestsClientDelay returns true if this error suggests a client delay as well as the
  392. // suggested seconds to wait, or false if the error does not imply a wait.
  393. func SuggestsClientDelay(err error) (int, bool) {
  394. switch t := err.(type) {
  395. case APIStatus:
  396. if t.Status().Details != nil {
  397. switch t.Status().Reason {
  398. case unversioned.StatusReasonServerTimeout, unversioned.StatusReasonTimeout:
  399. return int(t.Status().Details.RetryAfterSeconds), true
  400. }
  401. }
  402. }
  403. return 0, false
  404. }
  405. func reasonForError(err error) unversioned.StatusReason {
  406. switch t := err.(type) {
  407. case APIStatus:
  408. return t.Status().Reason
  409. }
  410. return unversioned.StatusReasonUnknown
  411. }