From 273d032db02901dba8deeb692f92ed2366dc6ee5 Mon Sep 17 00:00:00 2001 From: Petri Ovaska Date: Tue, 9 Nov 2021 08:28:48 +0200 Subject: [PATCH] Implement 'get' and 'get keys' -SDL CLI commands sdlcli get -command is used to display one or many resources. Usage: sdlcli get [command] sdlcli get keys -subcommand is used to list keys in the given namespace matching key search pattern. Usage: sdlcli get keys [pattern|default '*'] [flags] Issue-Id: RIC-113 Change-Id: I86b81ff8d8d2cdc0e959e285b19f93696ebe377e Signed-off-by: Petri Ovaska --- cmd/sdlcli/main.go | 8 +- internal/cli/cli_private_fn_test.go | 12 ++- internal/cli/get.go | 51 +++++++++++ internal/cli/get_test.go | 61 +++++++++++++ internal/cli/healthcheck.go | 7 +- internal/cli/healthcheck_test.go | 6 +- internal/cli/keys.go | 98 ++++++++++++++++++++ internal/cli/keys_test.go | 140 +++++++++++++++++++++++++++++ internal/cli/root.go | 17 +++- internal/cli/types.go | 22 +++++ internal/cli/utils.go | 6 ++ internal/mocks/db_mocks_private_testing.go | 9 ++ 12 files changed, 411 insertions(+), 26 deletions(-) create mode 100644 internal/cli/get.go create mode 100644 internal/cli/get_test.go create mode 100644 internal/cli/keys.go create mode 100644 internal/cli/keys_test.go diff --git a/cmd/sdlcli/main.go b/cmd/sdlcli/main.go index 8d12d1c..aec5e83 100644 --- a/cmd/sdlcli/main.go +++ b/cmd/sdlcli/main.go @@ -23,15 +23,9 @@ package main import ( - "fmt" "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/cli" - "os" ) func main() { - root := cli.NewRootCmd() - if err := root.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + cli.Execute() } diff --git a/internal/cli/cli_private_fn_test.go b/internal/cli/cli_private_fn_test.go index 831b5f9..78c6102 100644 --- a/internal/cli/cli_private_fn_test.go +++ b/internal/cli/cli_private_fn_test.go @@ -22,11 +22,9 @@ package cli -import ( - "github.com/spf13/cobra" +var ( + NewRootCmd = newRootCmd + NewHealthCheckCmd = newHealthCheckCmd + NewKeysCmdForTest = newKeysCmd + NewGetCmdForTest = newGetCmd ) - -// NewHealthCheckCmdForTest is used only in unit tests to mock database. -func NewHealthCheckCmdForTest(dbCreateCb DbCreateCb) *cobra.Command { - return newHealthCheckCmd(dbCreateCb) -} diff --git a/internal/cli/get.go b/internal/cli/get.go new file mode 100644 index 0000000..5577663 --- /dev/null +++ b/internal/cli/get.go @@ -0,0 +1,51 @@ +/* + Copyright (c) 2021 AT&T Intellectual Property. + Copyright (c) 2018-2021 Nokia. + + 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. +*/ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +package cli + +import ( + "github.com/spf13/cobra" +) + +var getCmd = newGetCmd() + +func init() { + rootCmd.AddCommand(getCmd) +} + +var ( + getLong = `Display one or many resources. + +Prints important information about the specified resources.` + + getExample = ` # List keys in the given namespace. + sdlcli get keys sdlns` +) + +func newGetCmd() *cobra.Command { + return &cobra.Command{ + Use: "get", + Short: "Display one or many resources", + Long: getLong, + Example: getExample, + } +} diff --git a/internal/cli/get_test.go b/internal/cli/get_test.go new file mode 100644 index 0000000..759ab11 --- /dev/null +++ b/internal/cli/get_test.go @@ -0,0 +1,61 @@ +/* + Copyright (c) 2021 AT&T Intellectual Property. + Copyright (c) 2018-2021 Nokia. + + 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. +*/ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +package cli_test + +import ( + "bytes" + "errors" + "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/cli" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetCmdShowHelp(t *testing.T) { + var expOkErr error + expHelp := "Display one or many resources.\n\nPrints important information about the specified resources." + expExamples := "Examples:\n # List keys in the given namespace." + expNokErr := errors.New("unknown flag: --ff") + tests := []struct { + args []string + expErr error + expOut string + }{ + {args: []string{"-h"}, expErr: expOkErr, expOut: expHelp}, + {args: []string{"--help"}, expErr: expOkErr, expOut: expHelp}, + {args: []string{}, expErr: expOkErr, expOut: expHelp}, + {args: []string{"--ff"}, expErr: expNokErr, expOut: expExamples}, + } + + for _, test := range tests { + buf := new(bytes.Buffer) + cmd := cli.NewGetCmdForTest() + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs(test.args) + err := cmd.Execute() + result := buf.String() + + assert.Equal(t, test.expErr, err) + assert.Contains(t, result, test.expOut) + } +} diff --git a/internal/cli/healthcheck.go b/internal/cli/healthcheck.go index aca1fda..2da97a3 100644 --- a/internal/cli/healthcheck.go +++ b/internal/cli/healthcheck.go @@ -23,15 +23,14 @@ package cli import ( - "bytes" "fmt" "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis" "github.com/spf13/cobra" "os" ) -func NewHealthCheckCmd() *cobra.Command { - return newHealthCheckCmd(newDatabase) +func init() { + rootCmd.AddCommand(newHealthCheckCmd(newDatabase)) } func newHealthCheckCmd(dbCreateCb DbCreateCb) *cobra.Command { @@ -40,8 +39,6 @@ func newHealthCheckCmd(dbCreateCb DbCreateCb) *cobra.Command { Short: "healthcheck - validates database healthiness", Long: `healthcheck - validates database healthiness`, RunE: func(cmd *cobra.Command, args []string) error { - var buf bytes.Buffer - sdlgoredis.SetDbLogger(&buf) out, err := runHealthCheck(dbCreateCb) cmd.Println(out) if err != nil { diff --git a/internal/cli/healthcheck_test.go b/internal/cli/healthcheck_test.go index e1de269..2cc0619 100644 --- a/internal/cli/healthcheck_test.go +++ b/internal/cli/healthcheck_test.go @@ -115,7 +115,7 @@ func newMockDatabase() *cli.Database { func runHcCli() (string, error) { buf := new(bytes.Buffer) - cmd := cli.NewHealthCheckCmdForTest(newMockDatabase) + cmd := cli.NewHealthCheckCmd(newMockDatabase) cmd.SetOut(buf) err := cmd.Execute() @@ -139,7 +139,7 @@ func TestCliHealthCheckCanShowHelp(t *testing.T) { for _, test := range tests { buf := new(bytes.Buffer) - cmd := cli.NewHealthCheckCmd() + cmd := cli.NewHealthCheckCmd(newMockDatabase) cmd.SetOut(buf) cmd.SetArgs([]string{test.args}) @@ -206,7 +206,7 @@ func TestCliHealthCheckCanShowHaDeploymentStatusCorrectlyWhenDbStateQueryFails(t expCliErr := errors.New("SDL CLI error: Some error") buf := new(bytes.Buffer) - cmd := cli.NewHealthCheckCmdForTest(newMockDatabase) + cmd := cli.NewHealthCheckCmd(newMockDatabase) cmd.SetErr(buf) err := cmd.Execute() diff --git a/internal/cli/keys.go b/internal/cli/keys.go new file mode 100644 index 0000000..33dce25 --- /dev/null +++ b/internal/cli/keys.go @@ -0,0 +1,98 @@ +/* + Copyright (c) 2021 AT&T Intellectual Property. + Copyright (c) 2018-2021 Nokia. + + 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. +*/ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +package cli + +import ( + "fmt" + "os" + "sort" + + "gerrit.o-ran-sc.org/r/ric-plt/sdlgo" + "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis" + "github.com/spf13/cobra" +) + +func init() { + getCmd.AddCommand(newKeysCmd(func() ISyncStorage { + return sdlgo.NewSyncStorage() + })) +} + +var ( + keysLong = `List keys in the given namespace matching key search pattern.` + + keysExample = ` # List all keys in the given namespace. + sdlcli get keys sdlns + # List keys in the given namespace matching given key search pattern. + sdlcli get keys sdlns 'he*' + + Supported search glob-style patterns: + h?llo matches hello, hallo and hxllo + h*llo matches hllo and heeeello + h[ae]llo matches hello and hallo, but not hillo + h[^e]llo matches hallo, hbllo, ... but not hello + h[a-b]llo matches hallo and hbllo + + The \ escapes character in key search pattern and those will be treated as a normal + character: + h\[?llo\* matches h[ello* and h[allo*` +) + +func newKeysCmd(sdlCb SyncStorageCreateCb) *cobra.Command { + return &cobra.Command{ + Use: "keys [pattern|default '*']", + Short: "List keys in the given namespace matching key search pattern", + Long: keysLong, + Example: keysExample, + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + sdlgoredis.SetDbLogger(&buf) + keysArgs := newKeysArgs(args[0], "*") + if len(args) > 1 { + keysArgs.pattern = args[1] + } + keys, err := runListKeys(sdlCb, keysArgs) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", buf.String()) + return err + } + printKeys(cmd, keys) + return nil + }, + } +} + +func runListKeys(sdlCb SyncStorageCreateCb, args keysArgs) ([]string, error) { + keys, err := sdlCb().ListKeys(args.ns, args.pattern) + if err != nil { + return nil, err + } + sort.Strings(keys) + return keys, err +} + +func printKeys(cmd *cobra.Command, keys []string) { + for _, k := range keys { + cmd.Println(k) + } +} diff --git a/internal/cli/keys_test.go b/internal/cli/keys_test.go new file mode 100644 index 0000000..bde5c24 --- /dev/null +++ b/internal/cli/keys_test.go @@ -0,0 +1,140 @@ +/* + Copyright (c) 2021 AT&T Intellectual Property. + Copyright (c) 2018-2021 Nokia. + + 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. +*/ + +/* + * This source code is part of the near-RT RIC (RAN Intelligent Controller) + * platform project (RICP). + */ + +package cli_test + +import ( + "bytes" + "errors" + "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/cli" + "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/mocks" + "github.com/stretchr/testify/assert" + "testing" +) + +var mKeys *keysMock + +type keysMock struct { + sdlIface *mocks.MockSdlApi + ns string + pattern string + keys []string + err error +} + +func setupKeysCliMock(ns, pattern string, keys []string, err error) { + mKeys = new(keysMock) + mKeys.ns = ns + mKeys.pattern = pattern + mKeys.keys = keys + mKeys.err = err +} + +func newMockSdlApi() cli.ISyncStorage { + mKeys.sdlIface = new(mocks.MockSdlApi) + mKeys.sdlIface.On("ListKeys", mKeys.ns, mKeys.pattern).Return(mKeys.keys, mKeys.err) + return mKeys.sdlIface +} + +func TestKeysCmdShowHelp(t *testing.T) { + var expOkErr error + expNokErrZeroArgs := errors.New("accepts between 1 and 2 arg(s), received 0") + expNokErrThreeArgs := errors.New("accepts between 1 and 2 arg(s), received 3") + expHelp := "Usage:\n keys [pattern|default '*'] [flags]" + tests := []struct { + args []string + expOut string + expErr error + }{ + {args: []string{"-h"}, expErr: expOkErr, expOut: expHelp}, + {args: []string{"--help"}, expErr: expOkErr, expOut: expHelp}, + {args: []string{}, expErr: expNokErrZeroArgs, expOut: expHelp}, + {args: []string{"ns", "key", "extra-arg"}, expErr: expNokErrThreeArgs, expOut: expHelp}, + } + + for _, test := range tests { + buf := new(bytes.Buffer) + cmd := cli.NewKeysCmdForTest(newMockSdlApi) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs(test.args) + err := cmd.Execute() + result := buf.String() + + assert.Equal(t, test.expErr, err) + assert.Contains(t, result, test.expOut) + } +} + +func TestKeysCmdSuccess(t *testing.T) { + var expOkErr error + argsNsPattern := []string{"testns", "*"} + argsNs := []string{"testns"} + tests := []struct { + args []string + outListKeys []string + expOut string + expErr error + }{ + {args: argsNs, outListKeys: []string{"key13"}, expOut: "key13", expErr: expOkErr}, + {args: argsNsPattern, outListKeys: []string{"key1"}, expOut: "key1", expErr: expOkErr}, + {args: argsNsPattern, outListKeys: []string{"key1", "key2"}, expOut: "key1\nkey2", expErr: expOkErr}, + {args: argsNsPattern, outListKeys: []string{"key2", "key3", "key1"}, expOut: "key1\nkey2\nkey3", expErr: expOkErr}, + {args: argsNsPattern, outListKeys: []string{}, expOut: "", expErr: expOkErr}, + } + + for _, test := range tests { + buf := new(bytes.Buffer) + if len(test.args) > 1 { + setupKeysCliMock(test.args[0], test.args[1], test.outListKeys, test.expErr) + } else { + setupKeysCliMock(test.args[0], "*", test.outListKeys, test.expErr) + } + cmd := cli.NewKeysCmdForTest(newMockSdlApi) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs(test.args) + err := cmd.Execute() + result := buf.String() + + assert.Equal(t, test.expErr, err) + assert.Contains(t, result, test.expOut) + } +} + +func TestKeysCmdFails(t *testing.T) { + argsNs := []string{"testns"} + expNokErr := errors.New("Boom!") + expNokOut := "Usage:\n keys [pattern|default '*'] [flags]" + + buf := new(bytes.Buffer) + setupKeysCliMock("testns", "*", []string{"very wrong"}, expNokErr) + cmd := cli.NewKeysCmdForTest(newMockSdlApi) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs(argsNs) + err := cmd.Execute() + result := buf.String() + + assert.Equal(t, expNokErr, err) + assert.Contains(t, result, expNokOut) +} diff --git a/internal/cli/root.go b/internal/cli/root.go index 6d20ebc..c5cf5ac 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -23,17 +23,26 @@ package cli import ( + "fmt" "github.com/spf13/cobra" + "os" ) -func NewRootCmd() *cobra.Command { - cmd := &cobra.Command{ +var rootCmd = newRootCmd() + +func newRootCmd() *cobra.Command { + return &cobra.Command{ Use: SdlCliApp, Short: "Shared Data Layer (SDL) troubleshooting command line tool", Long: `Shared Data Layer (SDL) troubleshooting command line tool`, Run: func(cmd *cobra.Command, args []string) { }, } - cmd.AddCommand(NewHealthCheckCmd()) - return cmd +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } } diff --git a/internal/cli/types.go b/internal/cli/types.go index de99e62..5321f0c 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -39,5 +39,27 @@ type Database struct { //DbCreateCb callback function type to create a new database type DbCreateCb func() *Database +//iSyncStorage is an interface towards SDL SyncStorage API +type ISyncStorage interface { + ListKeys(ns string, pattern string) ([]string, error) +} + +//SyncStorageCreateCb callback function type to create a new SyncStorageInterface +type SyncStorageCreateCb func() ISyncStorage + +//keysArgs struct is used for keys command arguments. +type keysArgs struct { + ns string + pattern string +} + +//newKeysArgs constructs a new keysArgs struct. +func newKeysArgs(ns string, pattern string) keysArgs { + return keysArgs{ + ns: ns, + pattern: pattern, + } +} + //SdlCliApp constant defines the name of the SDL CLI application const SdlCliApp = "sdlcli" diff --git a/internal/cli/utils.go b/internal/cli/utils.go index 3702050..87aa8ec 100644 --- a/internal/cli/utils.go +++ b/internal/cli/utils.go @@ -23,11 +23,17 @@ package cli import ( + "bytes" "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis" ) +var ( + buf bytes.Buffer +) + func newDatabase() *Database { db := &Database{} + sdlgoredis.SetDbLogger(&buf) for _, v := range sdlgoredis.Create() { db.Instances = append(db.Instances, v) } diff --git a/internal/mocks/db_mocks_private_testing.go b/internal/mocks/db_mocks_private_testing.go index 7832ee3..f6d0791 100644 --- a/internal/mocks/db_mocks_private_testing.go +++ b/internal/mocks/db_mocks_private_testing.go @@ -40,3 +40,12 @@ func (m *MockDB) State() (*sdlgoredis.DbState, error) { a := m.Called() return a.Get(0).(*sdlgoredis.DbState), a.Error(1) } + +type MockSdlApi struct { + mock.Mock +} + +func (m *MockSdlApi) ListKeys(ns string, pattern string) ([]string, error) { + a := m.Called(ns, pattern) + return a.Get(0).([]string), a.Error(1) +} -- 2.16.6