~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/apiserver/common/errors.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.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: 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
        ErrLoginExpired       = errors.New("login expired")
 
93
        ErrPerm               = errors.New("permission denied")
 
94
        ErrNotLoggedIn        = errors.New("not logged in")
 
95
        ErrUnknownWatcher     = errors.New("unknown watcher 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 &params.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
        ErrLoginExpired:              params.CodeLoginExpired,
 
126
        ErrPerm:                      params.CodeUnauthorized,
 
127
        ErrNotLoggedIn:               params.CodeUnauthorized,
 
128
        ErrUnknownWatcher:            params.CodeNotFound,
 
129
        ErrStoppedWatcher:            params.CodeStopped,
 
130
        ErrTryAgain:                  params.CodeTryAgain,
 
131
        ErrActionNotAvailable:        params.CodeActionNotAvailable,
 
132
}
 
133
 
 
134
func singletonCode(err error) (string, bool) {
 
135
        // All error types may not be hashable; deal with
 
136
        // that by catching the panic if we try to look up
 
137
        // a non-hashable type.
 
138
        defer func() {
 
139
                recover()
 
140
        }()
 
141
        code, ok := singletonErrorCodes[err]
 
142
        return code, ok
 
143
}
 
144
 
 
145
func singletonError(err error) (error, bool) {
 
146
        errCode := params.ErrCode(err)
 
147
        for singleton, code := range singletonErrorCodes {
 
148
                if errCode == code && singleton.Error() == err.Error() {
 
149
                        return singleton, true
 
150
                }
 
151
        }
 
152
        return nil, false
 
153
}
 
154
 
 
155
// ServerErrorAndStatus is like ServerError but also
 
156
// returns an HTTP status code appropriate for using
 
157
// in a response holding the given error.
 
158
func ServerErrorAndStatus(err error) (*params.Error, int) {
 
159
        err1 := ServerError(err)
 
160
        if err1 == nil {
 
161
                return nil, http.StatusOK
 
162
        }
 
163
        status := http.StatusInternalServerError
 
164
        switch err1.Code {
 
165
        case params.CodeUnauthorized:
 
166
                status = http.StatusUnauthorized
 
167
        case params.CodeNotFound,
 
168
                params.CodeUserNotFound,
 
169
                params.CodeModelNotFound:
 
170
                status = http.StatusNotFound
 
171
        case params.CodeBadRequest:
 
172
                status = http.StatusBadRequest
 
173
        case params.CodeMethodNotAllowed:
 
174
                status = http.StatusMethodNotAllowed
 
175
        case params.CodeOperationBlocked:
 
176
                // This should really be http.StatusForbidden but earlier versions
 
177
                // of juju clients rely on the 400 status, so we leave it like that.
 
178
                status = http.StatusBadRequest
 
179
        case params.CodeForbidden:
 
180
                status = http.StatusForbidden
 
181
        case params.CodeDischargeRequired:
 
182
                status = http.StatusUnauthorized
 
183
        case params.CodeRetry:
 
184
                status = http.StatusServiceUnavailable
 
185
        }
 
186
        return err1, status
 
187
}
 
188
 
 
189
// ServerError returns an error suitable for returning to an API
 
190
// client, with an error code suitable for various kinds of errors
 
191
// generated in packages outside the API.
 
192
func ServerError(err error) *params.Error {
 
193
        if err == nil {
 
194
                return nil
 
195
        }
 
196
        msg := err.Error()
 
197
        // Skip past annotations when looking for the code.
 
198
        err = errors.Cause(err)
 
199
        code, ok := singletonCode(err)
 
200
        var info *params.ErrorInfo
 
201
        switch {
 
202
        case ok:
 
203
        case isIOTimeout(err):
 
204
                code = params.CodeRetry
 
205
        case errors.IsUnauthorized(err):
 
206
                code = params.CodeUnauthorized
 
207
        case errors.IsNotFound(err):
 
208
                code = params.CodeNotFound
 
209
        case errors.IsUserNotFound(err):
 
210
                code = params.CodeUserNotFound
 
211
        case errors.IsAlreadyExists(err):
 
212
                code = params.CodeAlreadyExists
 
213
        case errors.IsNotAssigned(err):
 
214
                code = params.CodeNotAssigned
 
215
        case state.IsHasAssignedUnitsError(err):
 
216
                code = params.CodeHasAssignedUnits
 
217
        case state.IsHasHostedModelsError(err):
 
218
                code = params.CodeHasHostedModels
 
219
        case isNoAddressSetError(err):
 
220
                code = params.CodeNoAddressSet
 
221
        case errors.IsNotProvisioned(err):
 
222
                code = params.CodeNotProvisioned
 
223
        case IsUpgradeInProgressError(err):
 
224
                code = params.CodeUpgradeInProgress
 
225
        case state.IsHasAttachmentsError(err):
 
226
                code = params.CodeMachineHasAttachedStorage
 
227
        case isUnknownModelError(err):
 
228
                code = params.CodeModelNotFound
 
229
        case errors.IsNotSupported(err):
 
230
                code = params.CodeNotSupported
 
231
        case errors.IsBadRequest(err):
 
232
                code = params.CodeBadRequest
 
233
        case errors.IsMethodNotAllowed(err):
 
234
                code = params.CodeMethodNotAllowed
 
235
        default:
 
236
                if err, ok := err.(*DischargeRequiredError); ok {
 
237
                        code = params.CodeDischargeRequired
 
238
                        info = &params.ErrorInfo{
 
239
                                Macaroon: err.Macaroon,
 
240
                                // One macaroon fits all.
 
241
                                MacaroonPath: "/",
 
242
                        }
 
243
                        break
 
244
                }
 
245
                code = params.ErrCode(err)
 
246
        }
 
247
        return &params.Error{
 
248
                Message: msg,
 
249
                Code:    code,
 
250
                Info:    info,
 
251
        }
 
252
}
 
253
 
 
254
// Unfortunately there is no specific type of error for i/o timeout,
 
255
// and the error that bubbles up from mgo is annotated and a string type,
 
256
// so all we can do is look at the error suffix and see if it matches.
 
257
func isIOTimeout(err error) bool {
 
258
        // Perhaps sometime in the future, we'll have additional ways to tell if
 
259
        // the error is an i/o timeout type error, but for now this is all we
 
260
        // have.
 
261
        msg := err.Error()
 
262
        return strings.HasSuffix(msg, "i/o timeout")
 
263
}
 
264
 
 
265
func DestroyErr(desc string, ids, errs []string) 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
        return errors.Errorf("%s: %s", msg, strings.Join(errs, "; "))
 
276
}
 
277
 
 
278
// RestoreError makes a best effort at converting the given error
 
279
// back into an error originally converted by ServerError().
 
280
func RestoreError(err error) error {
 
281
        err = errors.Cause(err)
 
282
 
 
283
        if apiErr, ok := err.(*params.Error); !ok {
 
284
                return err
 
285
        } else if apiErr == nil {
 
286
                return nil
 
287
        }
 
288
        if params.ErrCode(err) == "" {
 
289
                return err
 
290
        }
 
291
        msg := err.Error()
 
292
 
 
293
        if singleton, ok := singletonError(err); ok {
 
294
                return singleton
 
295
        }
 
296
 
 
297
        // TODO(ericsnow) Support the other error types handled by ServerError().
 
298
        switch {
 
299
        case params.IsCodeUnauthorized(err):
 
300
                return errors.NewUnauthorized(nil, msg)
 
301
        case params.IsCodeNotFound(err):
 
302
                // TODO(ericsnow) UnknownModelError should be handled here too.
 
303
                // ...by parsing msg?
 
304
                return errors.NewNotFound(nil, msg)
 
305
        case params.IsCodeUserNotFound(err):
 
306
                return errors.NewUserNotFound(nil, msg)
 
307
        case params.IsCodeAlreadyExists(err):
 
308
                return errors.NewAlreadyExists(nil, msg)
 
309
        case params.IsCodeNotAssigned(err):
 
310
                return errors.NewNotAssigned(nil, msg)
 
311
        case params.IsCodeHasAssignedUnits(err):
 
312
                // TODO(ericsnow) Handle state.HasAssignedUnitsError here.
 
313
                // ...by parsing msg?
 
314
                return err
 
315
        case params.IsCodeHasHostedModels(err):
 
316
                return err
 
317
        case params.IsCodeNoAddressSet(err):
 
318
                // TODO(ericsnow) Handle isNoAddressSetError here.
 
319
                // ...by parsing msg?
 
320
                return err
 
321
        case params.IsCodeNotProvisioned(err):
 
322
                return errors.NewNotProvisioned(nil, msg)
 
323
        case params.IsCodeUpgradeInProgress(err):
 
324
                // TODO(ericsnow) Handle state.UpgradeInProgressError here.
 
325
                // ...by parsing msg?
 
326
                return err
 
327
        case params.IsCodeMachineHasAttachedStorage(err):
 
328
                // TODO(ericsnow) Handle state.HasAttachmentsError here.
 
329
                // ...by parsing msg?
 
330
                return err
 
331
        case params.IsCodeNotSupported(err):
 
332
                return errors.NewNotSupported(nil, msg)
 
333
        case params.IsBadRequest(err):
 
334
                return errors.NewBadRequest(nil, msg)
 
335
        case params.IsMethodNotAllowed(err):
 
336
                return errors.NewMethodNotAllowed(nil, msg)
 
337
        case params.ErrCode(err) == params.CodeDischargeRequired:
 
338
                // TODO(ericsnow) Handle DischargeRequiredError here.
 
339
                return err
 
340
        default:
 
341
                return err
 
342
        }
 
343
}