blob: e35a14aaee2c42026c73116e392a3b23807e1a72 [file] [log] [blame]
Scott Baker20481aa2019-06-20 11:00:54 -07001/*
2 * Portions copyright 2019-present Open Networking Foundation
3 * Original copyright 2019-present Ciena Corporation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package error
19
20/* Cordctl error classes
21
22 The basic idea is to throw specific error classes, so it's easier to test for them rather than doing string
23 comparisons or other ad hoc mechanisms for determining the type of error. This decouples the human
24 readable text of an error from programmatic testing of error type.
25
26 We differentiate between errors that we want to generate brief output, such as for example a
27 user mistyping a model name, versus errors that we want to generate additional context. This prevents
28 overwhelming a user with voluminous output for a simple mistake. A command-line option may be provided
29 to force full error output should it be desired.
30
31 Additionally, an added benefit is ease of maintenance and localisation, by locating all error text
32 in one place.
33
34 To return an error, for example:
35
36 return WithStackTrace(&ChecksumMismatchError{Actual: "123", Expected: "456"})
37
38 To check to see if a specific error was returned, either of the following are acceptable:
39
40 _, ok := err.(*ChecksumMismatchError)
41 ...
42
43 switch err.(type) {
44 case *ChecksumMismatchError:
45 ...
46*/
47
48import (
49 "bytes"
50 "fmt"
51 go_errors "github.com/go-errors/errors"
Scott Bakera55e6452019-06-25 11:10:30 -070052 "google.golang.org/grpc/status"
Scott Baker20481aa2019-06-20 11:00:54 -070053 "runtime"
54 "strings"
55)
56
57const (
58 MaxStackDepth = 50
59)
60
Scott Baker20481aa2019-06-20 11:00:54 -070061/* CordCtlError is the interface for errors created by cordctl.
62 * ShouldDumpStack()
63 * Returns false for well-understood problems such as invalid user input where a brief error message is sufficient
64 * Returns true for poorly-understood / unexpected problems where a full dump context may be useful
65 * Stack()
66 * Returns a string containing the stack trace where the error occurred
67 * Only useful if WithStackTrace() was called on the error
68 */
69
70type CordCtlError interface {
71 error
72 ShouldDumpStack() bool
73 Stack() string
74 AddStackTrace(skip int)
75}
76
Scott Bakera55e6452019-06-25 11:10:30 -070077/* ObjectReference contains information about the object that the error applies to.
78 This may be empty (ModelName="") or it may contain a ModelName together with
79 option Id or Queries.
80*/
81
82type ObjectReference struct {
83 ModelName string
84 Id int32
85 Queries map[string]string
86}
87
88// Returns true if the reference is populated
89func (f *ObjectReference) IsValid() bool {
90 return (f.ModelName != "")
91}
92
93func (f *ObjectReference) String() string {
94 if !f.IsValid() {
95 // The reference is empty
96 return ""
97 }
98
99 if f.Queries != nil {
100 kv := make([]string, 0, len(f.Queries))
101 for k, v := range f.Queries {
102 kv = append(kv, fmt.Sprintf("%s%s", k, v))
103 }
104 return fmt.Sprintf("%s <%v>", f.ModelName, strings.Join(kv, ", "))
105 }
106
107 if f.Id > 0 {
108 return fmt.Sprintf("%s <id=%d>", f.ModelName, f.Id)
109 }
110
111 return fmt.Sprintf("%s", f.ModelName)
112}
113
114// Returns " on model ModelName [id]" if the reference is populated, or "" otherwise.
115func (f *ObjectReference) Clause() string {
116 if !f.IsValid() {
117 // The reference is empty
118 return ""
119 }
120
121 return fmt.Sprintf(" [on model %s]", f.String())
122}
123
124/* BaseError
125 *
126 * Supports attaching stack traces to errors
Scott Baker20481aa2019-06-20 11:00:54 -0700127 * Borrowed the technique from github.com/go-errors. Decided against using go-errors directly since it requires
128 * wrapping our error classes. Instead, incorporated the stack trace directly into our error class.
Scott Bakera55e6452019-06-25 11:10:30 -0700129 *
130 * Also supports encapsulating error messages, so that a CordError can encapsulate the error message from a
131 * function that was called.
Scott Baker20481aa2019-06-20 11:00:54 -0700132 */
133
134type BaseError struct {
Scott Bakera55e6452019-06-25 11:10:30 -0700135 Obj ObjectReference
136 Encapsulated error // in case this error encapsulates an error from a lower level
137 stack []uintptr // for stack trace
138 frames []go_errors.StackFrame // for stack trace
Scott Baker20481aa2019-06-20 11:00:54 -0700139}
140
141func (f *BaseError) AddStackTrace(skip int) {
142 stack := make([]uintptr, MaxStackDepth)
143 length := runtime.Callers(2+skip, stack[:])
144 f.stack = stack[:length]
145}
146
147func (f *BaseError) Stack() string {
148 buf := bytes.Buffer{}
149
150 for _, frame := range f.StackFrames() {
151 buf.WriteString(frame.String())
152 }
153
154 return string(buf.Bytes())
155}
156
157func (f *BaseError) StackFrames() []go_errors.StackFrame {
158 if f.frames == nil {
159 f.frames = make([]go_errors.StackFrame, len(f.stack))
160
161 for i, pc := range f.stack {
162 f.frames[i] = go_errors.NewStackFrame(pc)
163 }
164 }
165
166 return f.frames
167}
168
169// ***************************************************************************
170// UserError is composed into Errors that are due to user input
171
172type UserError struct {
173 BaseError
174}
175
176func (f UserError) ShouldDumpStack() bool {
177 return false
178}
179
180// **************************************************************************
181// TransferError is composed into Errors that are due to failures in transfers
182
183type TransferError struct {
184 BaseError
185}
186
187func (f TransferError) ShouldDumpStack() bool {
188 return false
189}
190
191// ***************************************************************************
192// UnexpectedError is things that we don't expect to happen. They should
193// generate maximum error context, to provide useful information for developer
194// diagnosis.
195
196type UnexpectedError struct {
197 BaseError
198}
199
200func (f UnexpectedError) ShouldDumpStack() bool {
201 return true
202}
203
204// ***************************************************************************
205// Specific error classes follow
206
207// Checksum mismatch when downloading or uploading a file
208type ChecksumMismatchError struct {
209 TransferError
210 Name string // (optional) Name of file
211 Expected string
212 Actual string
213}
214
215func (f ChecksumMismatchError) Error() string {
216 if f.Name != "" {
Scott Bakera55e6452019-06-25 11:10:30 -0700217 return fmt.Sprintf("%s: checksum mismatch (actual=%s, expected=%s)", f.Name, f.Expected, f.Actual)
Scott Baker20481aa2019-06-20 11:00:54 -0700218 } else {
Scott Bakera55e6452019-06-25 11:10:30 -0700219 return fmt.Sprintf("checksum mismatch (actual=%s, expected=%s)", f.Expected, f.Actual)
Scott Baker20481aa2019-06-20 11:00:54 -0700220 }
221}
222
223// User specified a model type that is not valid
224type UnknownModelTypeError struct {
225 UserError
226 Name string // Name of model
227}
228
229func (f UnknownModelTypeError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700230 return fmt.Sprintf("Model %s does not exist. Use `cordctl modeltype list` to get a list of available models", f.Name)
Scott Baker20481aa2019-06-20 11:00:54 -0700231}
232
233// User specified a model state that is not valid
234type UnknownModelStateError struct {
235 UserError
236 Name string // Name of state
237}
238
239func (f UnknownModelStateError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700240 return fmt.Sprintf("Model state %s does not exist", f.Name)
Scott Baker20481aa2019-06-20 11:00:54 -0700241}
242
243// Command requires a filter be specified
244type FilterRequiredError struct {
245 UserError
246}
247
248func (f FilterRequiredError) Error() string {
249 return "Filter required. Use either an ID, --filter, or --all to specify which models to operate on"
250}
251
252// Command was aborted by the user
253type AbortedError struct {
254 UserError
255}
256
257func (f AbortedError) Error() string {
258 return "Aborted"
259}
260
261// Command was aborted by the user
262type NoMatchError struct {
263 UserError
264}
265
266func (f NoMatchError) Error() string {
267 return "No Match"
268}
269
270// User specified a field name that is not valid
271type FieldDoesNotExistError struct {
272 UserError
273 ModelName string
274 FieldName string
275}
276
277func (f FieldDoesNotExistError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700278 return fmt.Sprintf("Model %s does not have field %s", f.ModelName, f.FieldName)
Scott Baker20481aa2019-06-20 11:00:54 -0700279}
280
281// User specified a query string that is not properly formatted
282type IllegalQueryError struct {
283 UserError
284 Query string
285}
286
287func (f IllegalQueryError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700288 return fmt.Sprintf("Illegal query string %s", f.Query)
Scott Baker20481aa2019-06-20 11:00:54 -0700289}
290
291// We failed to type convert something that we thought should have converted
292type TypeConversionError struct {
293 UnexpectedError
294 Source string
295 Destination string
296}
297
298func (f TypeConversionError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700299 return fmt.Sprintf("Failed to type convert from %s to %s", f.Source, f.Destination)
Scott Baker20481aa2019-06-20 11:00:54 -0700300}
301
302// Version did not match a constraint
303type VersionConstraintError struct {
304 UserError
305 Name string
306 Version string
307 Constraint string
308}
309
310func (f VersionConstraintError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700311 return fmt.Sprintf("%s version %s did not match constraint '%s'", f.Name, f.Version, f.Constraint)
Scott Baker20481aa2019-06-20 11:00:54 -0700312}
313
314// A model was not found
315type ModelNotFoundError struct {
316 UserError
Scott Baker20481aa2019-06-20 11:00:54 -0700317}
318
319func (f ModelNotFoundError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700320 return fmt.Sprintf("Not Found%s", f.Obj.Clause())
321}
322
323// Permission Denied
324type PermissionDeniedError struct {
325 UserError
326}
327
328func (f PermissionDeniedError) Error() string {
329 return fmt.Sprintf("Permission Denied%s. Please verify username and password are correct", f.Obj.Clause())
Scott Baker20481aa2019-06-20 11:00:54 -0700330}
331
332// InvalidInputError is a catch-all for user mistakes that aren't covered elsewhere
333type InvalidInputError struct {
334 UserError
335 Message string
336}
337
338func (f InvalidInputError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700339 return fmt.Sprintf("%s", f.Message)
Scott Baker20481aa2019-06-20 11:00:54 -0700340}
341
342func NewInvalidInputError(format string, params ...interface{}) *InvalidInputError {
343 msg := fmt.Sprintf(format, params...)
344 err := &InvalidInputError{Message: msg}
345 err.AddStackTrace(2)
346 return err
347}
348
349// InternalError is a catch-all for errors that don't fit somewhere else
350type InternalError struct {
351 UnexpectedError
352 Message string
353}
354
355func (f InternalError) Error() string {
Scott Bakera55e6452019-06-25 11:10:30 -0700356 return fmt.Sprintf("Internal Error%s: %s", f.Obj.Clause(), f.Message)
Scott Baker20481aa2019-06-20 11:00:54 -0700357}
358
359func NewInternalError(format string, params ...interface{}) *InternalError {
360 msg := fmt.Sprintf(format, params...)
361 err := &InternalError{Message: msg}
362 err.AddStackTrace(2)
363 return err
364}
365
366// ***************************************************************************
367// Global exported function declarations
368
369// Attach a stack trace to an error. The error passed in must be a pointer to an error structure for the
370// CordCtlError interface to match.
371func WithStackTrace(err CordCtlError) error {
372 err.AddStackTrace(2)
373 return err
374}
375
Scott Bakera55e6452019-06-25 11:10:30 -0700376/* RpcErrorWithObjToCordError
377 *
378 * Convert an RPC error into a Cord Error. The ObjectReference allows methods to attach
379 * object-related information to the error, and this varies by method. For example the Delete()
380 * method comes with an ModelName and an Id. The List() method has only a ModelName.
381 *
382 * Stubs (RpcErrorWithModelNameToCordError) are provided below to make common usage more convenient.
383 */
Scott Baker20481aa2019-06-20 11:00:54 -0700384
Scott Bakera55e6452019-06-25 11:10:30 -0700385func RpcErrorWithObjToCordError(err error, obj ObjectReference) error {
Scott Baker20481aa2019-06-20 11:00:54 -0700386 if err == nil {
387 return err
388 }
Scott Bakera55e6452019-06-25 11:10:30 -0700389
390 st, ok := status.FromError(err)
391 if ok {
392 switch st.Code().String() {
393 case "PermissionDenied":
394 cordErr := &PermissionDeniedError{}
395 cordErr.Obj = obj
396 cordErr.Encapsulated = err
397 cordErr.AddStackTrace(2)
398 return cordErr
399 case "NotFound":
400 cordErr := &ModelNotFoundError{}
401 cordErr.Obj = obj
402 cordErr.Encapsulated = err
403 cordErr.AddStackTrace(2)
404 return cordErr
405 case "Unknown":
406 msg := st.Message()
407 if strings.HasPrefix(msg, "Exception calling application: ") {
408 msg = msg[31:]
409 }
410 cordErr := &InternalError{Message: msg}
411 cordErr.Obj = obj
412 cordErr.Encapsulated = err
413 cordErr.AddStackTrace(2)
414 return cordErr
415 }
Scott Baker20481aa2019-06-20 11:00:54 -0700416 }
Scott Bakera55e6452019-06-25 11:10:30 -0700417
Scott Baker20481aa2019-06-20 11:00:54 -0700418 return err
419}
420
Scott Bakera55e6452019-06-25 11:10:30 -0700421func RpcErrorToCordError(err error) error {
422 return RpcErrorWithObjToCordError(err, ObjectReference{})
423}
424
425func RpcErrorWithModelNameToCordError(err error, modelName string) error {
426 return RpcErrorWithObjToCordError(err, ObjectReference{ModelName: modelName})
427}
428
429func RpcErrorWithIdToCordError(err error, modelName string, id int32) error {
430 return RpcErrorWithObjToCordError(err, ObjectReference{ModelName: modelName, Id: id})
431}
432
433func RpcErrorWithQueriesToCordError(err error, modelName string, queries map[string]string) error {
434 return RpcErrorWithObjToCordError(err, ObjectReference{ModelName: modelName, Queries: queries})
Scott Baker20481aa2019-06-20 11:00:54 -0700435}