github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/errors.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "fmt" 8 "net/http" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/txn" 13 "gopkg.in/juju/names.v2" 14 "gopkg.in/macaroon.v2-unstable" 15 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/core/leadership" 18 "github.com/juju/juju/core/lease" 19 "github.com/juju/juju/state" 20 ) 21 22 func NotSupportedError(tag names.Tag, operation string) error { 23 return errors.Errorf("entity %q does not support %s", tag, operation) 24 } 25 26 type noAddressSetError struct { 27 unitTag names.UnitTag 28 addressName string 29 } 30 31 func (e *noAddressSetError) Error() string { 32 return fmt.Sprintf("%q has no %s address set", e.unitTag, e.addressName) 33 } 34 35 func NoAddressSetError(unitTag names.UnitTag, addressName string) error { 36 return &noAddressSetError{unitTag: unitTag, addressName: addressName} 37 } 38 39 func isNoAddressSetError(err error) bool { 40 _, ok := err.(*noAddressSetError) 41 return ok 42 } 43 44 type unknownModelError struct { 45 uuid string 46 } 47 48 func (e *unknownModelError) Error() string { 49 return fmt.Sprintf("unknown model: %q", e.uuid) 50 } 51 52 func UnknownModelError(uuid string) error { 53 return &unknownModelError{uuid: uuid} 54 } 55 56 func isUnknownModelError(err error) bool { 57 _, ok := err.(*unknownModelError) 58 return ok 59 } 60 61 // DischargeRequiredError is the error returned when a macaroon requires discharging 62 // to complete authentication. 63 type DischargeRequiredError struct { 64 Cause error 65 Macaroon *macaroon.Macaroon 66 } 67 68 // Error implements the error interface. 69 func (e *DischargeRequiredError) Error() string { 70 return e.Cause.Error() 71 } 72 73 // IsDischargeRequiredError reports whether the cause 74 // of the error is a *DischargeRequiredError. 75 func IsDischargeRequiredError(err error) bool { 76 _, ok := errors.Cause(err).(*DischargeRequiredError) 77 return ok 78 } 79 80 // IsUpgradeInProgress returns true if this error is caused 81 // by an upgrade in progress. 82 func IsUpgradeInProgressError(err error) bool { 83 if state.IsUpgradeInProgressError(err) { 84 return true 85 } 86 return errors.Cause(err) == params.UpgradeInProgressError 87 } 88 89 var ( 90 ErrBadId = errors.New("id not found") 91 ErrBadCreds = errors.New("invalid entity name or password") 92 ErrNoCreds = errors.New("no credentials provided") 93 ErrLoginExpired = errors.New("login expired") 94 ErrPerm = errors.New("permission denied") 95 ErrNotLoggedIn = errors.New("not logged in") 96 ErrUnknownWatcher = errors.New("unknown watcher id") 97 ErrStoppedWatcher = errors.New("watcher has been stopped") 98 ErrBadRequest = errors.New("invalid request") 99 ErrTryAgain = errors.New("try again") 100 ErrActionNotAvailable = errors.New("action no longer available") 101 ) 102 103 // OperationBlockedError returns an error which signifies that 104 // an operation has been blocked; the message should describe 105 // what has been blocked. 106 func OperationBlockedError(msg string) error { 107 if msg == "" { 108 msg = "the operation has been blocked" 109 } 110 return ¶ms.Error{ 111 Message: msg, 112 Code: params.CodeOperationBlocked, 113 } 114 } 115 116 var singletonErrorCodes = map[error]string{ 117 state.ErrCannotEnterScopeYet: params.CodeCannotEnterScopeYet, 118 state.ErrCannotEnterScope: params.CodeCannotEnterScope, 119 state.ErrUnitHasSubordinates: params.CodeUnitHasSubordinates, 120 state.ErrDead: params.CodeDead, 121 txn.ErrExcessiveContention: params.CodeExcessiveContention, 122 leadership.ErrClaimDenied: params.CodeLeadershipClaimDenied, 123 lease.ErrClaimDenied: params.CodeLeaseClaimDenied, 124 ErrBadId: params.CodeNotFound, 125 ErrBadCreds: params.CodeUnauthorized, 126 ErrNoCreds: params.CodeNoCreds, 127 ErrLoginExpired: params.CodeLoginExpired, 128 ErrPerm: params.CodeUnauthorized, 129 ErrNotLoggedIn: params.CodeUnauthorized, 130 ErrUnknownWatcher: params.CodeNotFound, 131 ErrStoppedWatcher: params.CodeStopped, 132 ErrTryAgain: params.CodeTryAgain, 133 ErrActionNotAvailable: params.CodeActionNotAvailable, 134 } 135 136 func singletonCode(err error) (string, bool) { 137 // All error types may not be hashable; deal with 138 // that by catching the panic if we try to look up 139 // a non-hashable type. 140 defer func() { 141 recover() 142 }() 143 code, ok := singletonErrorCodes[err] 144 return code, ok 145 } 146 147 func singletonError(err error) (error, bool) { 148 errCode := params.ErrCode(err) 149 for singleton, code := range singletonErrorCodes { 150 if errCode == code && singleton.Error() == err.Error() { 151 return singleton, true 152 } 153 } 154 return nil, false 155 } 156 157 // ServerErrorAndStatus is like ServerError but also 158 // returns an HTTP status code appropriate for using 159 // in a response holding the given error. 160 func ServerErrorAndStatus(err error) (*params.Error, int) { 161 err1 := ServerError(err) 162 if err1 == nil { 163 return nil, http.StatusOK 164 } 165 status := http.StatusInternalServerError 166 switch err1.Code { 167 case params.CodeUnauthorized: 168 status = http.StatusUnauthorized 169 case params.CodeNotFound, 170 params.CodeUserNotFound, 171 params.CodeModelNotFound: 172 status = http.StatusNotFound 173 case params.CodeBadRequest: 174 status = http.StatusBadRequest 175 case params.CodeMethodNotAllowed: 176 status = http.StatusMethodNotAllowed 177 case params.CodeOperationBlocked: 178 // This should really be http.StatusForbidden but earlier versions 179 // of juju clients rely on the 400 status, so we leave it like that. 180 status = http.StatusBadRequest 181 case params.CodeForbidden: 182 status = http.StatusForbidden 183 case params.CodeDischargeRequired: 184 status = http.StatusUnauthorized 185 case params.CodeRetry: 186 status = http.StatusServiceUnavailable 187 } 188 return err1, status 189 } 190 191 // ServerError returns an error suitable for returning to an API 192 // client, with an error code suitable for various kinds of errors 193 // generated in packages outside the API. 194 func ServerError(err error) *params.Error { 195 if err == nil { 196 return nil 197 } 198 logger.Tracef("server RPC error %v", errors.Details(err)) 199 msg := err.Error() 200 // Skip past annotations when looking for the code. 201 err = errors.Cause(err) 202 code, ok := singletonCode(err) 203 var info *params.ErrorInfo 204 switch { 205 case ok: 206 case errors.IsUnauthorized(err): 207 code = params.CodeUnauthorized 208 case errors.IsNotFound(err): 209 code = params.CodeNotFound 210 case errors.IsUserNotFound(err): 211 code = params.CodeUserNotFound 212 case errors.IsAlreadyExists(err): 213 code = params.CodeAlreadyExists 214 case errors.IsNotAssigned(err): 215 code = params.CodeNotAssigned 216 case state.IsHasAssignedUnitsError(err): 217 code = params.CodeHasAssignedUnits 218 case state.IsHasHostedModelsError(err): 219 code = params.CodeHasHostedModels 220 case state.IsHasPersistentStorageError(err): 221 code = params.CodeHasPersistentStorage 222 case state.IsModelNotEmptyError(err): 223 code = params.CodeModelNotEmpty 224 case isNoAddressSetError(err): 225 code = params.CodeNoAddressSet 226 case errors.IsNotProvisioned(err): 227 code = params.CodeNotProvisioned 228 case IsUpgradeInProgressError(err): 229 code = params.CodeUpgradeInProgress 230 case state.IsHasAttachmentsError(err): 231 code = params.CodeMachineHasAttachedStorage 232 case state.IsStorageAttachedError(err): 233 code = params.CodeStorageAttached 234 case isUnknownModelError(err): 235 code = params.CodeModelNotFound 236 case errors.IsNotSupported(err): 237 code = params.CodeNotSupported 238 case errors.IsBadRequest(err): 239 code = params.CodeBadRequest 240 case errors.IsMethodNotAllowed(err): 241 code = params.CodeMethodNotAllowed 242 case errors.IsNotImplemented(err): 243 code = params.CodeNotImplemented 244 case state.IsIncompatibleSeriesError(err): 245 code = params.CodeIncompatibleSeries 246 default: 247 if err, ok := err.(*DischargeRequiredError); ok { 248 code = params.CodeDischargeRequired 249 info = ¶ms.ErrorInfo{ 250 Macaroon: err.Macaroon, 251 // One macaroon fits all. 252 MacaroonPath: "/", 253 } 254 break 255 } 256 code = params.ErrCode(err) 257 } 258 return ¶ms.Error{ 259 Message: msg, 260 Code: code, 261 Info: info, 262 } 263 } 264 265 func DestroyErr(desc string, ids []string, errs []error) error { 266 // TODO(waigani) refactor DestroyErr to take a map of ids to errors. 267 if len(errs) == 0 { 268 return nil 269 } 270 msg := "some %s were not destroyed" 271 if len(errs) == len(ids) { 272 msg = "no %s were destroyed" 273 } 274 msg = fmt.Sprintf(msg, desc) 275 errStrings := make([]string, len(errs)) 276 for i, err := range errs { 277 errStrings[i] = err.Error() 278 } 279 return errors.Errorf("%s: %s", msg, strings.Join(errStrings, "; ")) 280 } 281 282 // RestoreError makes a best effort at converting the given error 283 // back into an error originally converted by ServerError(). 284 func RestoreError(err error) error { 285 err = errors.Cause(err) 286 287 if apiErr, ok := err.(*params.Error); !ok { 288 return err 289 } else if apiErr == nil { 290 return nil 291 } 292 if params.ErrCode(err) == "" { 293 return err 294 } 295 msg := err.Error() 296 297 if singleton, ok := singletonError(err); ok { 298 return singleton 299 } 300 301 // TODO(ericsnow) Support the other error types handled by ServerError(). 302 switch { 303 case params.IsCodeUnauthorized(err): 304 return errors.NewUnauthorized(nil, msg) 305 case params.IsCodeNotFound(err): 306 // TODO(ericsnow) UnknownModelError should be handled here too. 307 // ...by parsing msg? 308 return errors.NewNotFound(nil, msg) 309 case params.IsCodeUserNotFound(err): 310 return errors.NewUserNotFound(nil, msg) 311 case params.IsCodeAlreadyExists(err): 312 return errors.NewAlreadyExists(nil, msg) 313 case params.IsCodeNotAssigned(err): 314 return errors.NewNotAssigned(nil, msg) 315 case params.IsCodeHasAssignedUnits(err): 316 // TODO(ericsnow) Handle state.HasAssignedUnitsError here. 317 // ...by parsing msg? 318 return err 319 case params.IsCodeHasHostedModels(err): 320 return err 321 case params.IsCodeHasPersistentStorage(err): 322 return err 323 case params.IsCodeModelNotEmpty(err): 324 return err 325 case params.IsCodeNoAddressSet(err): 326 // TODO(ericsnow) Handle isNoAddressSetError here. 327 // ...by parsing msg? 328 return err 329 case params.IsCodeNotProvisioned(err): 330 return errors.NewNotProvisioned(nil, msg) 331 case params.IsCodeUpgradeInProgress(err): 332 // TODO(ericsnow) Handle state.UpgradeInProgressError here. 333 // ...by parsing msg? 334 return err 335 case params.IsCodeMachineHasAttachedStorage(err): 336 // TODO(ericsnow) Handle state.HasAttachmentsError here. 337 // ...by parsing msg? 338 return err 339 case params.IsCodeStorageAttached(err): 340 return err 341 case params.IsCodeNotSupported(err): 342 return errors.NewNotSupported(nil, msg) 343 case params.IsBadRequest(err): 344 return errors.NewBadRequest(nil, msg) 345 case params.IsMethodNotAllowed(err): 346 return errors.NewMethodNotAllowed(nil, msg) 347 case params.ErrCode(err) == params.CodeDischargeRequired: 348 // TODO(ericsnow) Handle DischargeRequiredError here. 349 return err 350 default: 351 return err 352 } 353 }