SEBA-688 add model tests
Change-Id: Ia50dc7aae5529a6e005645bc7461944caa82a329
diff --git a/commands/backup.go b/commands/backup.go
index 9d11db9..287236f 100644
--- a/commands/backup.go
+++ b/commands/backup.go
@@ -17,6 +17,7 @@
package commands
import (
+ "context"
"errors"
"fmt"
flags "github.com/jessevdk/go-flags"
@@ -67,6 +68,8 @@
}
defer conn.Close()
+ ctx := context.Background() // TODO: Implement a sync timeout
+
// We might close and reopen the connection befor we do the DownloadFile,
// so make sure we've downloaded the service descriptor.
_, err = descriptor.FindSymbol("xos.filetransfer")
@@ -90,7 +93,7 @@
// STEP 2: Wait for the operation to complete
flags := GM_UNTIL_ENACTED | GM_UNTIL_FOUND | Ternary_uint32(options.Quiet, GM_QUIET, 0)
- conn, completed_backupop, err := GetModelWithRetry(conn, descriptor, "BackupOperation", backupop["id"].(int32), flags)
+ conn, completed_backupop, err := GetModelWithRetry(ctx, conn, descriptor, "BackupOperation", backupop["id"].(int32), flags)
if err != nil {
return err
}
@@ -111,7 +114,7 @@
return errors.New("BackupOp.file_id is not set")
}
- completed_backupfile, err := GetModel(conn, descriptor, "BackupFile", backupfile_id)
+ completed_backupfile, err := GetModel(ctx, conn, descriptor, "BackupFile", backupfile_id)
if err != nil {
return err
}
@@ -156,6 +159,8 @@
}
defer conn.Close()
+ ctx := context.Background() // TODO: Implement a sync timeout
+
local_name := options.Args.LocalFileName
remote_name := "cordctl-restore-" + time.Now().Format("20060102T150405Z")
uri := "file:///var/run/xos/backup/local/" + remote_name
@@ -209,7 +214,7 @@
flags := GM_UNTIL_ENACTED | GM_UNTIL_FOUND | GM_UNTIL_STATUS | Ternary_uint32(options.Quiet, GM_QUIET, 0)
queries := map[string]string{"uuid": backupop["uuid"].(string)}
- conn, completed_backupop, err := FindModelWithRetry(conn, descriptor, "BackupOperation", queries, flags)
+ conn, completed_backupop, err := FindModelWithRetry(ctx, conn, descriptor, "BackupOperation", queries, flags)
if err != nil {
return err
}
diff --git a/commands/models.go b/commands/models.go
index 33c3793..fd820b2 100644
--- a/commands/models.go
+++ b/commands/models.go
@@ -17,10 +17,12 @@
package commands
import (
+ "context"
"fmt"
flags "github.com/jessevdk/go-flags"
"github.com/jhump/protoreflect/dynamic"
"strings"
+ "time"
)
const (
@@ -45,12 +47,13 @@
type ModelUpdate struct {
OutputOptions
- Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
- Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
- SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
- SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
- Sync bool `long:"sync" description:"Synchronize before returning"`
- Args struct {
+ Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
+ Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
+ SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
+ SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
+ Sync bool `long:"sync" description:"Synchronize before returning"`
+ SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for --sync option"`
+ Args struct {
ModelName ModelNameString
} `positional-args:"yes" required:"yes"`
IDArgs struct {
@@ -72,20 +75,22 @@
type ModelCreate struct {
OutputOptions
- Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output"`
- SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
- SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
- Sync bool `long:"sync" description:"Synchronize before returning"`
- Args struct {
+ Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output"`
+ SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
+ SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
+ Sync bool `long:"sync" description:"Synchronize before returning"`
+ SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for --sync option"`
+ Args struct {
ModelName ModelNameString
} `positional-args:"yes" required:"yes"`
}
type ModelSync struct {
OutputOptions
- Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
- Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
- Args struct {
+ Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
+ Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
+ SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for synchronization"`
+ Args struct {
ModelName ModelNameString
} `positional-args:"yes" required:"yes"`
IDArgs struct {
@@ -165,7 +170,7 @@
return err
}
- models, err := ListOrFilterModels(conn, descriptor, string(options.Args.ModelName), queries)
+ models, err := ListOrFilterModels(context.Background(), conn, descriptor, string(options.Args.ModelName), queries)
if err != nil {
return err
}
@@ -255,13 +260,13 @@
if len(options.IDArgs.ID) > 0 {
models = make([]*dynamic.Message, len(options.IDArgs.ID))
for i, id := range options.IDArgs.ID {
- models[i], err = GetModel(conn, descriptor, modelName, id)
+ models[i], err = GetModel(context.Background(), conn, descriptor, modelName, id)
if err != nil {
return err
}
}
} else {
- models, err = ListOrFilterModels(conn, descriptor, modelName, queries)
+ models, err = ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
if err != nil {
return err
}
@@ -300,11 +305,13 @@
}
if options.Sync {
+ ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
+ defer cancel()
for i, model := range models {
id := model.GetFieldByName("id").(int32)
if modelStatusOutput.Rows[i].Message == "Updated" {
conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
- conn, _, err = GetModelWithRetry(conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
+ conn, _, err = GetModelWithRetry(ctx, conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
conditional_printf(!options.Quiet, "\n")
UpdateModelStatusOutput(&modelStatusOutput, i, id, "Enacted", err, true)
}
@@ -348,7 +355,7 @@
if len(options.IDArgs.ID) > 0 {
ids = options.IDArgs.ID
} else {
- models, err := ListOrFilterModels(conn, descriptor, modelName, queries)
+ models, err := ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
if err != nil {
return err
}
@@ -419,10 +426,12 @@
UpdateModelStatusOutput(&modelStatusOutput, 0, fields["id"], "Created", err, !options.Sync)
if options.Sync {
+ ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
+ defer cancel()
if modelStatusOutput.Rows[0].Message == "Created" {
id := fields["id"].(int32)
conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
- conn, _, err = GetModelWithRetry(conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
+ conn, _, err = GetModelWithRetry(ctx, conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
conditional_printf(!options.Quiet, "\n")
UpdateModelStatusOutput(&modelStatusOutput, 0, id, "Enacted", err, true)
}
@@ -465,7 +474,7 @@
if len(options.IDArgs.ID) > 0 {
ids = options.IDArgs.ID
} else {
- models, err := ListOrFilterModels(conn, descriptor, modelName, queries)
+ models, err := ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
if err != nil {
return err
}
@@ -482,10 +491,13 @@
}
}
+ ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
+ defer cancel()
+
modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(ids))
for i, id := range ids {
conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
- conn, _, err = GetModelWithRetry(conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
+ conn, _, err = GetModelWithRetry(ctx, conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
conditional_printf(!options.Quiet, "\n")
UpdateModelStatusOutput(&modelStatusOutput, i, id, "Enacted", err, true)
}
diff --git a/commands/models_test.go b/commands/models_test.go
new file mode 100644
index 0000000..2b44f8f
--- /dev/null
+++ b/commands/models_test.go
@@ -0,0 +1,376 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+ "bytes"
+ "github.com/opencord/cordctl/testutils"
+ "testing"
+ "time"
+)
+
+func TestModelList(t *testing.T) {
+ // use `python -m json.tool` to pretty-print json
+ expected := `[
+ {
+ "controller_kind": "",
+ "controller_replica_count": 0,
+ "creator_id": 0,
+ "default_flavor_id": 0,
+ "default_image_id": 0,
+ "default_isolation": "",
+ "default_node_id": 0,
+ "description": "",
+ "enabled": false,
+ "exposed_ports": "",
+ "id": 1,
+ "max_instances": 0,
+ "mount_data_sets": "",
+ "name": "mockslice1",
+ "network": "",
+ "principal_id": 0,
+ "service_id": 0,
+ "site_id": 1,
+ "trust_domain_id": 0
+ },
+ {
+ "controller_kind": "",
+ "controller_replica_count": 0,
+ "creator_id": 0,
+ "default_flavor_id": 0,
+ "default_image_id": 0,
+ "default_isolation": "",
+ "default_node_id": 0,
+ "description": "",
+ "enabled": false,
+ "exposed_ports": "",
+ "id": 2,
+ "max_instances": 0,
+ "mount_data_sets": "",
+ "name": "mockslice2",
+ "network": "",
+ "principal_id": 0,
+ "service_id": 0,
+ "site_id": 1,
+ "trust_domain_id": 0
+ }
+ ]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.List.Args.ModelName = "Slice"
+ options.List.OutputAs = "json"
+ err := options.List.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelListFilterID(t *testing.T) {
+ // use `python -m json.tool` to pretty-print json
+ expected := `[
+ {
+ "controller_kind": "",
+ "controller_replica_count": 0,
+ "creator_id": 0,
+ "default_flavor_id": 0,
+ "default_image_id": 0,
+ "default_isolation": "",
+ "default_node_id": 0,
+ "description": "",
+ "enabled": false,
+ "exposed_ports": "",
+ "id": 1,
+ "max_instances": 0,
+ "mount_data_sets": "",
+ "name": "mockslice1",
+ "network": "",
+ "principal_id": 0,
+ "service_id": 0,
+ "site_id": 1,
+ "trust_domain_id": 0
+ }
+ ]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.List.Args.ModelName = "Slice"
+ options.List.OutputAs = "json"
+ options.List.Filter = "id=1"
+ err := options.List.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelListFilterName(t *testing.T) {
+ // use `python -m json.tool` to pretty-print json
+ expected := `[
+ {
+ "controller_kind": "",
+ "controller_replica_count": 0,
+ "creator_id": 0,
+ "default_flavor_id": 0,
+ "default_image_id": 0,
+ "default_isolation": "",
+ "default_node_id": 0,
+ "description": "",
+ "enabled": false,
+ "exposed_ports": "",
+ "id": 2,
+ "max_instances": 0,
+ "mount_data_sets": "",
+ "name": "mockslice2",
+ "network": "",
+ "principal_id": 0,
+ "service_id": 0,
+ "site_id": 1,
+ "trust_domain_id": 0
+ }
+ ]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.List.Args.ModelName = "Slice"
+ options.List.OutputAs = "json"
+ options.List.Filter = "name=mockslice2"
+ err := options.List.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelUpdate(t *testing.T) {
+ expected := `[{"id":1, "message":"Updated"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Update.Args.ModelName = "Slice"
+ options.Update.OutputAs = "json"
+ options.Update.IDArgs.ID = []int32{1}
+ options.Update.SetFields = "name=mockslice1_newname"
+ err := options.Update.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelUpdateUsingFilter(t *testing.T) {
+ expected := `[{"id":1, "message":"Updated"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Update.Args.ModelName = "Slice"
+ options.Update.OutputAs = "json"
+ options.Update.Filter = "id=1"
+ options.Update.SetFields = "name=mockslice1_newname"
+ err := options.Update.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelUpdateNoExist(t *testing.T) {
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Update.Args.ModelName = "Slice"
+ options.Update.OutputAs = "json"
+ 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.")
+}
+
+func TestModelUpdateUsingFilterNoExist(t *testing.T) {
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Update.Args.ModelName = "Slice"
+ options.Update.OutputAs = "json"
+ options.Update.Filter = "id=77"
+ options.Update.SetFields = "name=mockslice1_newname"
+ err := options.Update.Execute([]string{})
+
+ testutils.AssertErrorEqual(t, err, "Filter matches no objects")
+}
+
+func TestModelCreate(t *testing.T) {
+ expected := `[{"id":3, "message":"Created"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Create.Args.ModelName = "Slice"
+ options.Create.OutputAs = "json"
+ options.Create.SetFields = "name=mockslice3,site_id=1"
+ err := options.Create.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelDelete(t *testing.T) {
+ expected := `[{"id":1, "message":"Deleted"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Delete.Args.ModelName = "Slice"
+ options.Delete.OutputAs = "json"
+ options.Delete.IDArgs.ID = []int32{1}
+ err := options.Delete.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelDeleteUsingFilter(t *testing.T) {
+ expected := `[{"id":1, "message":"Deleted"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Delete.Args.ModelName = "Slice"
+ options.Delete.OutputAs = "json"
+ options.Delete.Filter = "id=1"
+ err := options.Delete.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelDeleteNoExist(t *testing.T) {
+ expected := `[{"id":77, "message":"Slice matching query does not exist."}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Delete.Args.ModelName = "Slice"
+ options.Delete.OutputAs = "json"
+ options.Delete.IDArgs.ID = []int32{77}
+ err := options.Delete.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelDeleteFilterNoExist(t *testing.T) {
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Delete.Args.ModelName = "Slice"
+ options.Delete.OutputAs = "json"
+ options.Delete.Filter = "id=77"
+ err := options.Delete.Execute([]string{})
+ testutils.AssertErrorEqual(t, err, "Filter matches no objects")
+}
+
+func TestModelSync(t *testing.T) {
+ expected := `[{"id":1, "message":"Enacted"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Sync.Args.ModelName = "Slice"
+ options.Sync.OutputAs = "json"
+ options.Sync.IDArgs.ID = []int32{1}
+ options.Sync.SyncTimeout = 5 * time.Second
+ err := options.Sync.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestModelSyncTimeout(t *testing.T) {
+ expected := `[{"id":2, "message":"context deadline exceeded"}]`
+
+ got := new(bytes.Buffer)
+ OutputStream = got
+
+ var options ModelOpts
+ options.Sync.Args.ModelName = "Slice"
+ options.Sync.OutputAs = "json"
+ options.Sync.IDArgs.ID = []int32{2}
+ options.Sync.SyncTimeout = 5 * time.Second
+ err := options.Sync.Execute([]string{})
+
+ if err != nil {
+ t.Errorf("%s: Received error %v", t.Name(), err)
+ return
+ }
+
+ testutils.AssertJSONEqual(t, got.String(), expected)
+}
diff --git a/commands/orm.go b/commands/orm.go
index 87dee55..a817ea5 100644
--- a/commands/orm.go
+++ b/commands/orm.go
@@ -307,8 +307,8 @@
}
// Get a model from XOS given its ID
-func GetModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
- ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+func GetModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
+ ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
defer cancel()
headers := GenerateHeaders()
@@ -334,7 +334,7 @@
}
// Get a model, but retry under a variety of circumstances
-func GetModelWithRetry(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
+func GetModelWithRetry(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
quiet := (flags & GM_QUIET) != 0
until_found := (flags & GM_UNTIL_FOUND) != 0
until_enacted := (flags & GM_UNTIL_ENACTED) != 0
@@ -350,14 +350,18 @@
}
}
- model, err := GetModel(conn, descriptor, modelName, id)
+ model, err := GetModel(ctx, conn, descriptor, modelName, id)
if err != nil {
if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
if !quiet {
fmt.Print(".")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
conn.Close()
conn = nil
continue
@@ -367,7 +371,11 @@
if !quiet {
fmt.Print("x")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
continue
}
return nil, nil, err
@@ -377,7 +385,11 @@
if !quiet {
fmt.Print("o")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
continue
}
@@ -385,7 +397,11 @@
if !quiet {
fmt.Print("O")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
continue
}
@@ -402,8 +418,8 @@
}
// List all objects of a given model
-func ListModels(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
- ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+func ListModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
+ ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
defer cancel()
headers := GenerateHeaders()
@@ -435,8 +451,8 @@
// queries is a map of <field_name> to <operator><query>
// For example,
// map[string]string{"name": "==mysite"}
-func FilterModels(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
- ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+func FilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
+ ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
defer cancel()
headers := GenerateHeaders()
@@ -481,17 +497,17 @@
}
// Call ListModels or FilterModels as appropriate
-func ListOrFilterModels(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
+func ListOrFilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) ([]*dynamic.Message, error) {
if len(queries) == 0 {
- return ListModels(conn, descriptor, modelName)
+ return ListModels(ctx, conn, descriptor, modelName)
} else {
- return FilterModels(conn, descriptor, modelName, queries)
+ return FilterModels(ctx, conn, descriptor, modelName, queries)
}
}
// Get a model from XOS given a fieldName/fieldValue
-func FindModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) (*dynamic.Message, error) {
- models, err := FilterModels(conn, descriptor, modelName, queries)
+func FindModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string) (*dynamic.Message, error) {
+ models, err := FilterModels(ctx, conn, descriptor, modelName, queries)
if err != nil {
return nil, err
}
@@ -504,7 +520,7 @@
}
// Find a model, but retry under a variety of circumstances
-func FindModelWithRetry(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
+func FindModelWithRetry(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, queries map[string]string, flags uint32) (*grpc.ClientConn, *dynamic.Message, error) {
quiet := (flags & GM_QUIET) != 0
until_found := (flags & GM_UNTIL_FOUND) != 0
until_enacted := (flags & GM_UNTIL_ENACTED) != 0
@@ -520,14 +536,18 @@
}
}
- model, err := FindModel(conn, descriptor, modelName, queries)
+ model, err := FindModel(ctx, conn, descriptor, modelName, queries)
if err != nil {
if strings.Contains(err.Error(), "rpc error: code = Unavailable") ||
strings.Contains(err.Error(), "rpc error: code = Internal desc = stream terminated by RST_STREAM") {
if !quiet {
fmt.Print(".")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
conn.Close()
conn = nil
continue
@@ -537,7 +557,11 @@
if !quiet {
fmt.Print("x")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
continue
}
return nil, nil, err
@@ -547,7 +571,11 @@
if !quiet {
fmt.Print("o")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
continue
}
@@ -555,7 +583,11 @@
if !quiet {
fmt.Print("O")
}
- time.Sleep(100 * time.Millisecond)
+ select {
+ case <-time.After(100 * time.Millisecond):
+ case <-ctx.Done():
+ return nil, nil, ctx.Err()
+ }
continue
}
diff --git a/commands/setup_test.go b/commands/setup_test.go
new file mode 100644
index 0000000..5c1ec83
--- /dev/null
+++ b/commands/setup_test.go
@@ -0,0 +1,35 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+ "fmt"
+ "github.com/opencord/cordctl/testutils"
+ "os"
+ "testing"
+)
+
+// This TestMain is global to all tests in the `commands` package
+
+func TestMain(m *testing.M) {
+ err := testutils.StartMockServer("data.json")
+ if err != nil {
+ fmt.Printf("Error when initializing mock server %v", err)
+ os.Exit(-1)
+ }
+ os.Exit(m.Run())
+}
diff --git a/commands/version_test.go b/commands/version_test.go
index 1ebe7c3..6835fc2 100644
--- a/commands/version_test.go
+++ b/commands/version_test.go
@@ -18,9 +18,6 @@
import (
"bytes"
- "fmt"
- "github.com/opencord/cordctl/testutils"
- "os"
"testing"
)
@@ -89,12 +86,3 @@
t.Errorf("%s: expected and received did not match", t.Name())
}
}
-
-func TestMain(m *testing.M) {
- err := testutils.StartMockServer("data.json")
- if err != nil {
- fmt.Printf("Error when initializing mock server %v", err)
- os.Exit(-1)
- }
- os.Exit(m.Run())
-}
diff --git a/mock/data.json b/mock/data.json
index 2c610f9..710f423 100644
--- a/mock/data.json
+++ b/mock/data.json
@@ -13,22 +13,112 @@
},
{
"method": "GetSlice",
- "input": ".*",
+ "input": {"id": 1},
"output": {
"id": 1,
"name": "mockslice1",
- "site_id": 1
+ "site_id": 1,
+ "updated": 1234.0,
+ "enacted": 1234.0
}
},
{
+ "method": "GetSlice",
+ "input": {"id": 2},
+ "output": {
+ "id": 2,
+ "name": "mockslice2",
+ "site_id": 1,
+ "updated": 1234.0,
+ "enacted": 900.0
+ }
+ },
+ {
+ "method": "GetSlice",
+ "input": {"id": 77},
+ "error": { "code": 2, "message": "Slice matching query does not exist."}
+ },
+ {
"method": "ListSlice",
"input": ".*",
"output": {
"items": [{
"id": 1,
"name": "mockslice1",
+ "site_id": 1,
+ "updated": 1234.0,
+ "enacted": 1234.0
+ },
+ {
+ "id": 2,
+ "name": "mockslice2",
+ "site_id": 1,
+ "updated": 1234.0,
+ "enacted": 900.0
+ }]
+ }
+ },
+ {
+ "method": "FilterSlice",
+ "input": {"elements": [{"operator": 0, "name": "id", "iValue": 1}]},
+ "output": {
+ "items": [{
+ "id": 1,
+ "name": "mockslice1",
"site_id": 1
}]
}
+ },
+ {
+ "method": "FilterSlice",
+ "input": {"elements": [{"operator": 0, "name": "name", "sValue": "mockslice2"}]},
+ "output": {
+ "items": [{
+ "id": 2,
+ "name": "mockslice2",
+ "site_id": 1
+ }]
+ }
+ },
+ {
+ "method": "FilterSlice",
+ "input": {"elements": [{"operator": 0, "name": "id", "iValue": 77}]},
+ "output": {
+ "items": []
+ }
+ },
+ {
+ "method": "UpdateSlice",
+ "input": {"id": 1, "name": "mockslice1_newname"},
+ "output": {
+ "id": 1,
+ "name": "mockslice1_newname",
+ "site_id": 1
+ }
+ },
+ {
+ "method": "UpdateSlice",
+ "input": { "id": 77, "name": "mockslice1_newname"},
+ "error": { "code": 2, "message": "Slice matching query does not exist."}
+ },
+ {
+ "method": "CreateSlice",
+ "input": {"name": "mockslice3", "site_id": 1},
+ "output": {
+ "id": 3,
+ "name": "mockslice3",
+ "site_id": 1
+ }
+ },
+ {
+ "method": "DeleteSlice",
+ "input": {"id": 1},
+ "output": {}
+ },
+ {
+ "method": "DeleteSlice",
+ "input": {"id": 77},
+ "error": { "code": 2, "message": "Slice matching query does not exist."}
}
+
]
diff --git a/testutils/testutils.go b/testutils/testutils.go
index da290b8..64fdbcb 100644
--- a/testutils/testutils.go
+++ b/testutils/testutils.go
@@ -16,10 +16,14 @@
package testutils
import (
+ "encoding/json"
+ "errors"
"fmt"
"os"
"os/exec"
+ "reflect"
"strings"
+ "testing"
)
const (
@@ -91,3 +95,39 @@
return strings.Contains(string(out), "Listening for requests"), nil
}
+
+// Assert that two JSON-encoded strings are equal
+func AssertJSONEqual(t *testing.T, actual string, expected string) error {
+ var expected_json interface{}
+ err := json.Unmarshal([]byte(expected), &expected_json)
+ if err != nil {
+ t.Errorf("Failed to unmarshal expected json %s", expected)
+ return err
+ }
+
+ var actual_json interface{}
+ err = json.Unmarshal([]byte(actual), &actual_json)
+ if err != nil {
+ t.Errorf("Failed to unmarshal actual json %s", actual_json)
+ return err
+ }
+
+ if !reflect.DeepEqual(expected_json, actual_json) {
+ t.Errorf("Actual json does not match expected json\nACTUAL:\n%s\nEXPECTED:\n%s", actual, expected)
+ }
+
+ return nil
+}
+
+// Assert that the error string is what we expect
+func AssertErrorEqual(t *testing.T, err error, expected string) error {
+ if err == nil {
+ t.Error("Expected an error, but received nil")
+ return errors.New("AssertErrorEqual")
+ }
+ if err.Error() != expected {
+ t.Errorf("Expected error `%s` but received actual error `%s`", expected, err.Error())
+ return errors.New("AssertErrorEqual")
+ }
+ return nil
+}