SEBA-705 better error reporting
Change-Id: Id461c6efe2d0b7ab9c0d1ddb72482d10899b16fe
diff --git a/commands/backup.go b/commands/backup.go
index de435fb..ceda54a 100644
--- a/commands/backup.go
+++ b/commands/backup.go
@@ -18,9 +18,8 @@
import (
"context"
- "errors"
- "fmt"
flags "github.com/jessevdk/go-flags"
+ corderrors "github.com/opencord/cordctl/error"
"time"
)
@@ -107,13 +106,13 @@
// we've failed. leave.
if status != "created" {
- return errors.New("BackupOp status is " + status)
+ return corderrors.NewInternalError("BackupOp status is %s", status)
}
// STEP 3: Retrieve URI
backupfile_id := completed_backupop.GetFieldByName("file_id").(int32)
if backupfile_id == 0 {
- return errors.New("BackupOp.file_id is not set")
+ return corderrors.NewInternalError("BackupOp.file_id is not set")
}
completed_backupfile, err := GetModel(ctx, conn, descriptor, "BackupFile", backupfile_id)
@@ -136,9 +135,9 @@
// STEP 5: Verify checksum
if completed_backupfile.GetFieldByName("checksum").(string) != h.GetChecksum() {
- return fmt.Errorf("Checksum mismatch, received=%s, expected=%s",
- h.GetChecksum(),
- completed_backupfile.GetFieldByName("checksum").(string))
+ return corderrors.WithStackTrace(&corderrors.ChecksumMismatchError{
+ Actual: h.GetChecksum(),
+ Expected: completed_backupfile.GetFieldByName("checksum").(string)})
}
// STEP 6: Show results
@@ -188,15 +187,15 @@
upload_status := GetEnumValue(upload_result, "status")
if upload_status != "SUCCESS" {
- return errors.New("Upload status was " + upload_status)
+ return corderrors.NewInternalError("Upload status was %s", upload_status)
}
// STEP 2: Verify checksum
if upload_result.GetFieldByName("checksum").(string) != h.GetChecksum() {
- return fmt.Errorf("Checksum mismatch, expected=%s, received=%s",
- h.GetChecksum(),
- upload_result.GetFieldByName("checksum").(string))
+ return corderrors.WithStackTrace(&corderrors.ChecksumMismatchError{
+ Expected: h.GetChecksum(),
+ Actual: upload_result.GetFieldByName("checksum").(string)})
}
// STEP 2: Create a BackupFile object
diff --git a/commands/common.go b/commands/common.go
index bb26273..12b869f 100644
--- a/commands/common.go
+++ b/commands/common.go
@@ -23,6 +23,7 @@
versionUtils "github.com/hashicorp/go-version"
"github.com/jhump/protoreflect/dynamic"
"github.com/jhump/protoreflect/grpcreflect"
+ corderrors "github.com/opencord/cordctl/error"
"golang.org/x/net/context"
"google.golang.org/grpc"
reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
@@ -113,8 +114,10 @@
}
if !constraint.Check(serverVersion) {
- return nil, nil, fmt.Errorf("Core version %s does not match constraint '%s'",
- serverVersion, CORE_VERSION_CONSTRAINT)
+ return nil, nil, corderrors.WithStackTrace(&corderrors.VersionConstraintError{
+ Name: "xos-core",
+ Version: serverVersion.String(),
+ Constraint: CORE_VERSION_CONSTRAINT})
}
}
diff --git a/commands/models.go b/commands/models.go
index 49e85d8..e539bd9 100644
--- a/commands/models.go
+++ b/commands/models.go
@@ -22,6 +22,7 @@
"github.com/fullstorydev/grpcurl"
flags "github.com/jessevdk/go-flags"
"github.com/jhump/protoreflect/dynamic"
+ corderrors "github.com/opencord/cordctl/error"
"google.golang.org/grpc"
"sort"
"strings"
@@ -190,7 +191,7 @@
val, ok := kindMap[strings.ToLower(kindArg)]
if !ok {
- return "", fmt.Errorf("Failed to understand model state %s", kindArg)
+ return "", corderrors.WithStackTrace(&corderrors.UnknownModelStateError{Name: kindArg})
}
return val, nil
@@ -216,7 +217,7 @@
}
if (exclusiveCount == 0) || (exclusiveCount > 1) {
- return nil, fmt.Errorf("Use either an ID, --filter, or --all to specify which models to operate on")
+ return nil, corderrors.WithStackTrace(&corderrors.FilterRequiredError{})
}
queries, err := CommaSeparatedQueryToMap(filter, true)
@@ -236,10 +237,10 @@
ids[i] = model.GetFieldByName("id").(int32)
}
if len(ids) == 0 {
- return nil, fmt.Errorf("Filter matches no objects")
+ return nil, corderrors.WithStackTrace(&corderrors.NoMatchError{})
} else if len(ids) > 1 {
if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
- return nil, fmt.Errorf("Aborted by user")
+ return nil, corderrors.WithStackTrace(&corderrors.AbortedError{})
}
}
}
@@ -353,7 +354,7 @@
if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
(len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
- return fmt.Errorf("Use either an ID or a --filter to specify which models to update")
+ return corderrors.WithStackTrace(&corderrors.FilterRequiredError{})
}
queries, err := CommaSeparatedQueryToMap(options.Filter, true)
@@ -386,10 +387,10 @@
}
if len(models) == 0 {
- return fmt.Errorf("Filter matches no objects")
+ return corderrors.WithStackTrace(&corderrors.NoMatchError{})
} else if len(models) > 1 {
if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
- return fmt.Errorf("Aborted by user")
+ return corderrors.WithStackTrace(&corderrors.AbortedError{})
}
}
diff --git a/commands/models_test.go b/commands/models_test.go
index c4f6c47..3a6031b 100644
--- a/commands/models_test.go
+++ b/commands/models_test.go
@@ -2,7 +2,7 @@
* Portions copyright 2019-present Open Networking Foundation
* Original copyright 2019-present Ciena Corporation
*
- * Licensed under the Apache License, Version 2.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the"github.com/stretchr/testify/assert" "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
@@ -18,7 +18,9 @@
import (
"bytes"
+ corderrors "github.com/opencord/cordctl/error"
"github.com/opencord/cordctl/testutils"
+ "github.com/stretchr/testify/assert"
"testing"
"time"
)
@@ -267,7 +269,9 @@
options.Update.IDArgs.ID = []int32{77}
options.Update.SetFields = "name=mockslice1_newname"
err := options.Update.Execute([]string{})
- testutils.AssertErrorEqual(t, err, "rpc error: code = Unknown desc = Slice matching query does not exist.")
+
+ _, matched := err.(*corderrors.ModelNotFoundError)
+ assert.True(t, matched)
}
func TestModelUpdateUsingFilterNoExist(t *testing.T) {
@@ -281,7 +285,8 @@
options.Update.SetFields = "name=mockslice1_newname"
err := options.Update.Execute([]string{})
- testutils.AssertErrorEqual(t, err, "Filter matches no objects")
+ _, matched := err.(*corderrors.NoMatchError)
+ assert.True(t, matched)
}
func TestModelCreate(t *testing.T) {
@@ -373,7 +378,9 @@
options.Delete.OutputAs = "json"
options.Delete.Filter = "id=77"
err := options.Delete.Execute([]string{})
- testutils.AssertErrorEqual(t, err, "Filter matches no objects")
+
+ _, matched := err.(*corderrors.NoMatchError)
+ assert.True(t, matched)
}
func TestModelSync(t *testing.T) {
diff --git a/commands/orm.go b/commands/orm.go
index 5938698..f57f21d 100644
--- a/commands/orm.go
+++ b/commands/orm.go
@@ -18,13 +18,13 @@
import (
"context"
- "errors"
"fmt"
"github.com/fullstorydev/grpcurl"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/dynamic"
+ corderrors "github.com/opencord/cordctl/error"
"google.golang.org/grpc"
"io"
"strconv"
@@ -65,7 +65,7 @@
if strings.HasPrefix(query, "!=") {
return strings.TrimSpace(query[2:]), "EQUAL", true, nil
} else if strings.HasPrefix(query, "==") {
- return "", "", false, errors.New("Operator == is now allowed. Suggest using = instead.")
+ return "", "", false, corderrors.NewInvalidInputError("Operator == is now allowed. Suggest using = instead.")
} else if strings.HasPrefix(query, "=") {
return strings.TrimSpace(query[1:]), "EQUAL", false, nil
} else if strings.HasPrefix(query, ">=") {
@@ -109,7 +109,7 @@
field_descriptor := h.Model.FindFieldByName(field_name)
if field_descriptor == nil {
- return fmt.Errorf("Field %s does not exist", field_name)
+ return corderrors.WithStackTrace(&corderrors.FieldDoesNotExistError{ModelName: h.Model.GetName(), FieldName: field_name})
}
field_type := field_descriptor.GetType()
@@ -123,9 +123,9 @@
i, err = strconv.ParseInt(value, 10, 32)
nm.SetFieldByName("iValue", uint32(i))
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
- err = errors.New("Floating point filters are unsupported")
+ err = corderrors.NewInvalidInputError("Floating point filters are unsupported")
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
- err = errors.New("Floating point filters are unsupported")
+ err = corderrors.NewInvalidInputError("Floating point filters are unsupported")
default:
nm.SetFieldByName("sValue", value)
err = nil
@@ -168,7 +168,7 @@
}
}
if operator_pos == -1 {
- return nil, fmt.Errorf("Illegal operator/value string %s", query_str)
+ return nil, corderrors.WithStackTrace(&corderrors.IllegalQueryError{Query: query_str})
}
queries[strings.TrimSpace(query_str[:operator_pos])] = query_str[operator_pos:]
}
@@ -193,11 +193,11 @@
}
model_md, ok := model_descriptor.(*desc.MessageDescriptor)
if !ok {
- return nil, fmt.Errorf("Failed to convert model %s to a messagedescriptor", modelName)
+ return nil, corderrors.WithStackTrace(&corderrors.TypeConversionError{Source: modelName, Destination: "messageDescriptor"})
}
field_descriptor := model_md.FindFieldByName(field_name)
if field_descriptor == nil {
- return nil, fmt.Errorf("Field %s does not exist in model %s", field_name, modelName)
+ return nil, corderrors.WithStackTrace(&corderrors.FieldDoesNotExistError{ModelName: modelName, FieldName: field_name})
}
field_type := field_descriptor.GetType()
@@ -254,7 +254,7 @@
}
_, present := models[name]
if !present {
- return errors.New("Model " + name + " does not exist. Use `cordctl models available` to get a list of available models")
+ return corderrors.WithStackTrace(&corderrors.UnknownModelTypeError{Name: name})
}
return nil
}
@@ -330,11 +330,11 @@
}
err := grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.xos.Get"+modelName, headers, h, h.GetParams)
if err != nil {
- return nil, err
+ return nil, corderrors.RpcErrorWithIdToCordError(err, modelName, id)
}
if h.Status != nil && h.Status.Err() != nil {
- return nil, h.Status.Err()
+ return nil, corderrors.RpcErrorWithIdToCordError(h.Status.Err(), modelName, id) //h.Status.Err()
}
d, err := dynamic.AsDynamicMessage(h.Response)
@@ -379,7 +379,8 @@
continue
}
- if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
+ _, is_not_found_error := err.(*corderrors.ModelNotFoundError)
+ if until_found && is_not_found_error {
if !quiet {
fmt.Print("x")
}
@@ -475,7 +476,7 @@
}
model_md, ok := model_descriptor.(*desc.MessageDescriptor)
if !ok {
- return nil, errors.New("Failed to convert model to a messagedescriptor")
+ return nil, corderrors.WithStackTrace(&corderrors.TypeConversionError{Source: modelName, Destination: "messageDescriptor"})
}
h := &QueryEventHandler{
@@ -525,7 +526,7 @@
}
if len(models) == 0 {
- return nil, errors.New("rpc error: code = NotFound")
+ return nil, corderrors.WithStackTrace(&corderrors.ModelNotFoundError{ModelName: modelName, Queries: queries})
}
return models[0], nil
@@ -565,7 +566,8 @@
continue
}
- if until_found && strings.Contains(err.Error(), "rpc error: code = NotFound") {
+ _, is_not_found_error := err.(*corderrors.ModelNotFoundError)
+ if until_found && is_not_found_error {
if !quiet {
fmt.Print("x")
}
diff --git a/commands/orm_test.go b/commands/orm_test.go
index 1050de1..9a0dad1 100644
--- a/commands/orm_test.go
+++ b/commands/orm_test.go
@@ -18,6 +18,7 @@
import (
"context"
+ corderrors "github.com/opencord/cordctl/error"
"github.com/stretchr/testify/assert"
"testing"
)
@@ -60,7 +61,7 @@
assert.Equal(t, err, nil)
}
-func TestCommaSeparatedQueryStringsToMap(t *testing.T) {
+func TestCommaSeparatedQueryToMap(t *testing.T) {
m, err := CommaSeparatedQueryToMap("foo=7,bar!=stuff, x = 5, y= 27", true)
assert.Equal(t, err, nil)
assert.Equal(t, m["foo"], "=7")
@@ -69,6 +70,28 @@
assert.Equal(t, m["y"], "= 27")
}
+func TestCommaSeparatedQueryToMapIllegal(t *testing.T) {
+ // Query string missing operator
+ _, err := CommaSeparatedQueryToMap("foo", true)
+
+ _, matched := err.(*corderrors.IllegalQueryError)
+ assert.True(t, matched)
+
+ // Query string is contains an empty element
+ _, err = CommaSeparatedQueryToMap(",foo=bar", true)
+
+ _, matched = err.(*corderrors.IllegalQueryError)
+ assert.True(t, matched)
+}
+
+func TestCommaSeparatedQueryToMapEmpty(t *testing.T) {
+ // Query string missing operator
+ m, err := CommaSeparatedQueryToMap("", true)
+
+ assert.Equal(t, err, nil)
+ assert.Equal(t, len(m), 0)
+}
+
func TestTypeConvert(t *testing.T) {
conn, descriptor, err := InitClient(INIT_DEFAULT)
assert.Equal(t, err, nil)
@@ -96,7 +119,8 @@
assert.Equal(t, err, nil)
err = CheckModelName(descriptor, "DoesNotExist")
- assert.Equal(t, err.Error(), "Model DoesNotExist does not exist. Use `cordctl models available` to get a list of available models")
+ _, matched := err.(*corderrors.UnknownModelTypeError)
+ assert.True(t, matched)
}
func TestCreateModel(t *testing.T) {
@@ -139,6 +163,18 @@
assert.Equal(t, m.GetFieldByName("name").(string), "mockslice1")
}
+func TestGetModelNoExist(t *testing.T) {
+ conn, descriptor, err := InitClient(INIT_DEFAULT)
+ assert.Equal(t, err, nil)
+ defer conn.Close()
+
+ _, err = GetModel(context.Background(), conn, descriptor, "Slice", int32(77))
+ assert.NotEqual(t, err, nil)
+
+ _, matched := err.(*corderrors.ModelNotFoundError)
+ assert.True(t, matched)
+}
+
func TestListModels(t *testing.T) {
conn, descriptor, err := InitClient(INIT_DEFAULT)
assert.Equal(t, err, nil)
@@ -169,6 +205,34 @@
assert.Equal(t, m[0].GetFieldByName("name").(string), "mockslice1")
}
+func TestFindModel(t *testing.T) {
+ conn, descriptor, err := InitClient(INIT_DEFAULT)
+ assert.Equal(t, err, nil)
+ defer conn.Close()
+
+ qm := map[string]string{"id": "=1"}
+
+ m, err := FindModel(context.Background(), conn, descriptor, "Slice", qm)
+ assert.Equal(t, err, nil)
+
+ assert.Equal(t, m.GetFieldByName("id").(int32), int32(1))
+ assert.Equal(t, m.GetFieldByName("name").(string), "mockslice1")
+}
+
+func TestFindModelNoExist(t *testing.T) {
+ conn, descriptor, err := InitClient(INIT_DEFAULT)
+ assert.Equal(t, err, nil)
+ defer conn.Close()
+
+ qm := map[string]string{"id": "=77"}
+
+ _, err = FindModel(context.Background(), conn, descriptor, "Slice", qm)
+ assert.NotEqual(t, err, nil)
+
+ _, matched := err.(*corderrors.ModelNotFoundError)
+ assert.True(t, matched)
+}
+
func TestDeleteModel(t *testing.T) {
conn, descriptor, err := InitClient(INIT_DEFAULT)
assert.Equal(t, err, nil)
diff --git a/commands/transfer.go b/commands/transfer.go
index 229f0fd..e746058 100644
--- a/commands/transfer.go
+++ b/commands/transfer.go
@@ -17,9 +17,8 @@
package commands
import (
- "errors"
- "fmt"
flags "github.com/jessevdk/go-flags"
+ corderrors "github.com/opencord/cordctl/error"
"strings"
)
@@ -76,11 +75,11 @@
uri := options.Args.URI
if IsFileUri(local_name) {
- return errors.New("local_name argument should not be a uri")
+ return corderrors.NewInvalidInputError("local_name argument should not be a uri")
}
if !IsFileUri(uri) {
- return errors.New("uri argument should be a file:// uri")
+ return corderrors.NewInvalidInputError("uri argument should be a file:// uri")
}
h, upload_result, err := UploadFile(conn, descriptor, local_name, uri, options.ChunkSize)
@@ -89,9 +88,9 @@
}
if upload_result.GetFieldByName("checksum").(string) != h.GetChecksum() {
- return fmt.Errorf("Checksum mismatch, expected=%s, received=%s",
- h.GetChecksum(),
- upload_result.GetFieldByName("checksum").(string))
+ return corderrors.WithStackTrace(&corderrors.ChecksumMismatchError{
+ Expected: h.GetChecksum(),
+ Actual: upload_result.GetFieldByName("checksum").(string)})
}
data := make([]TransferOutput, 1)
@@ -120,11 +119,11 @@
uri := options.Args.URI
if IsFileUri(local_name) {
- return errors.New("local_name argument should not be a uri")
+ return corderrors.NewInvalidInputError("local_name argument should not be a uri")
}
if !IsFileUri(uri) {
- return errors.New("uri argument should be a file:// uri")
+ return corderrors.NewInvalidInputError("uri argument should be a file:// uri")
}
h, err := DownloadFile(conn, descriptor, uri, local_name)