github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/names" 13 "github.com/juju/txn" 14 "gopkg.in/macaroon.v1" 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, 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 ErrPerm = errors.New("permission denied") 93 ErrNotLoggedIn = errors.New("not logged in") 94 ErrUnknownWatcher = errors.New("unknown watcher id") 95 ErrUnknownPinger = errors.New("unknown pinger id") 96 ErrStoppedWatcher = errors.New("watcher has been stopped") 97 ErrBadRequest = errors.New("invalid request") 98 ErrTryAgain = errors.New("try again") 99 ErrActionNotAvailable = errors.New("action no longer available") 100 ) 101 102 // OperationBlockedError returns an error which signifies that 103 // an operation has been blocked; the message should describe 104 // what has been blocked. 105 func OperationBlockedError(msg string) error { 106 if msg == "" { 107 msg = "the operation has been blocked" 108 } 109 return ¶ms.Error{ 110 Message: msg, 111 Code: params.CodeOperationBlocked, 112 } 113 } 114 115 var singletonErrorCodes = map[error]string{ 116 state.ErrCannotEnterScopeYet: params.CodeCannotEnterScopeYet, 117 state.ErrCannotEnterScope: params.CodeCannotEnterScope, 118 state.ErrUnitHasSubordinates: params.CodeUnitHasSubordinates, 119 state.ErrDead: params.CodeDead, 120 txn.ErrExcessiveContention: params.CodeExcessiveContention, 121 leadership.ErrClaimDenied: params.CodeLeadershipClaimDenied, 122 lease.ErrClaimDenied: params.CodeLeaseClaimDenied, 123 ErrBadId: params.CodeNotFound, 124 ErrBadCreds: params.CodeUnauthorized, 125 ErrPerm: params.CodeUnauthorized, 126 ErrNotLoggedIn: params.CodeUnauthorized, 127 ErrUnknownWatcher: params.CodeNotFound, 128 ErrStoppedWatcher: params.CodeStopped, 129 ErrTryAgain: params.CodeTryAgain, 130 ErrActionNotAvailable: params.CodeActionNotAvailable, 131 } 132 133 func singletonCode(err error) (string, bool) { 134 // All error types may not be hashable; deal with 135 // that by catching the panic if we try to look up 136 // a non-hashable type. 137 defer func() { 138 recover() 139 }() 140 code, ok := singletonErrorCodes[err] 141 return code, ok 142 } 143 144 func singletonError(err error) (error, bool) { 145 errCode := params.ErrCode(err) 146 for singleton, code := range singletonErrorCodes { 147 if errCode == code && singleton.Error() == err.Error() { 148 return singleton, true 149 } 150 } 151 return nil, false 152 } 153 154 // ServerErrorAndStatus is like ServerError but also 155 // returns an HTTP status code appropriate for using 156 // in a response holding the given error. 157 func ServerErrorAndStatus(err error) (*params.Error, int) { 158 err1 := ServerError(err) 159 if err1 == nil { 160 return nil, http.StatusOK 161 } 162 status := http.StatusInternalServerError 163 switch err1.Code { 164 case params.CodeUnauthorized: 165 status = http.StatusUnauthorized 166 case params.CodeNotFound: 167 status = http.StatusNotFound 168 case params.CodeBadRequest: 169 status = http.StatusBadRequest 170 case params.CodeMethodNotAllowed: 171 status = http.StatusMethodNotAllowed 172 case params.CodeOperationBlocked: 173 // This should really be http.StatusForbidden but earlier versions 174 // of juju clients rely on the 400 status, so we leave it like that. 175 status = http.StatusBadRequest 176 case params.CodeForbidden: 177 status = http.StatusForbidden 178 case params.CodeDischargeRequired: 179 status = http.StatusUnauthorized 180 } 181 return err1, status 182 } 183 184 // ServerError returns an error suitable for returning to an API 185 // client, with an error code suitable for various kinds of errors 186 // generated in packages outside the API. 187 func ServerError(err error) *params.Error { 188 if err == nil { 189 return nil 190 } 191 msg := err.Error() 192 // Skip past annotations when looking for the code. 193 err = errors.Cause(err) 194 code, ok := singletonCode(err) 195 var info *params.ErrorInfo 196 switch { 197 case ok: 198 case errors.IsUnauthorized(err): 199 code = params.CodeUnauthorized 200 case errors.IsNotFound(err): 201 code = params.CodeNotFound 202 case errors.IsAlreadyExists(err): 203 code = params.CodeAlreadyExists 204 case errors.IsNotAssigned(err): 205 code = params.CodeNotAssigned 206 case state.IsHasAssignedUnitsError(err): 207 code = params.CodeHasAssignedUnits 208 case state.IsHasHostedModelsError(err): 209 code = params.CodeHasHostedModels 210 case isNoAddressSetError(err): 211 code = params.CodeNoAddressSet 212 case errors.IsNotProvisioned(err): 213 code = params.CodeNotProvisioned 214 case IsUpgradeInProgressError(err): 215 code = params.CodeUpgradeInProgress 216 case state.IsHasAttachmentsError(err): 217 code = params.CodeMachineHasAttachedStorage 218 case isUnknownModelError(err): 219 code = params.CodeNotFound 220 case errors.IsNotSupported(err): 221 code = params.CodeNotSupported 222 case errors.IsBadRequest(err): 223 code = params.CodeBadRequest 224 case errors.IsMethodNotAllowed(err): 225 code = params.CodeMethodNotAllowed 226 default: 227 if err, ok := err.(*DischargeRequiredError); ok { 228 code = params.CodeDischargeRequired 229 info = ¶ms.ErrorInfo{ 230 Macaroon: err.Macaroon, 231 // One macaroon fits all. 232 MacaroonPath: "/", 233 } 234 break 235 } 236 code = params.ErrCode(err) 237 } 238 return ¶ms.Error{ 239 Message: msg, 240 Code: code, 241 Info: info, 242 } 243 } 244 245 func DestroyErr(desc string, ids, errs []string) error { 246 // TODO(waigani) refactor DestroyErr to take a map of ids to errors. 247 if len(errs) == 0 { 248 return nil 249 } 250 msg := "some %s were not destroyed" 251 if len(errs) == len(ids) { 252 msg = "no %s were destroyed" 253 } 254 msg = fmt.Sprintf(msg, desc) 255 return errors.Errorf("%s: %s", msg, strings.Join(errs, "; ")) 256 } 257 258 // RestoreError makes a best effort at converting the given error 259 // back into an error originally converted by ServerError(). 260 func RestoreError(err error) error { 261 err = errors.Cause(err) 262 263 if apiErr, ok := err.(*params.Error); !ok { 264 return err 265 } else if apiErr == nil { 266 return nil 267 } 268 if params.ErrCode(err) == "" { 269 return err 270 } 271 msg := err.Error() 272 273 if singleton, ok := singletonError(err); ok { 274 return singleton 275 } 276 277 // TODO(ericsnow) Support the other error types handled by ServerError(). 278 switch { 279 case params.IsCodeUnauthorized(err): 280 return errors.NewUnauthorized(nil, msg) 281 case params.IsCodeNotFound(err): 282 // TODO(ericsnow) UnknownModelError should be handled here too. 283 // ...by parsing msg? 284 return errors.NewNotFound(nil, msg) 285 case params.IsCodeAlreadyExists(err): 286 return errors.NewAlreadyExists(nil, msg) 287 case params.IsCodeNotAssigned(err): 288 return errors.NewNotAssigned(nil, msg) 289 case params.IsCodeHasAssignedUnits(err): 290 // TODO(ericsnow) Handle state.HasAssignedUnitsError here. 291 // ...by parsing msg? 292 return err 293 case params.IsCodeHasHostedModels(err): 294 return err 295 case params.IsCodeNoAddressSet(err): 296 // TODO(ericsnow) Handle isNoAddressSetError here. 297 // ...by parsing msg? 298 return err 299 case params.IsCodeNotProvisioned(err): 300 return errors.NewNotProvisioned(nil, msg) 301 case params.IsCodeUpgradeInProgress(err): 302 // TODO(ericsnow) Handle state.UpgradeInProgressError here. 303 // ...by parsing msg? 304 return err 305 case params.IsCodeMachineHasAttachedStorage(err): 306 // TODO(ericsnow) Handle state.HasAttachmentsError here. 307 // ...by parsing msg? 308 return err 309 case params.IsCodeNotSupported(err): 310 return errors.NewNotSupported(nil, msg) 311 case params.IsBadRequest(err): 312 return errors.NewBadRequest(nil, msg) 313 case params.IsMethodNotAllowed(err): 314 return errors.NewMethodNotAllowed(nil, msg) 315 case params.ErrCode(err) == params.CodeDischargeRequired: 316 // TODO(ericsnow) Handle DischargeRequiredError here. 317 return err 318 default: 319 return err 320 } 321 }