Implement 'get' and 'get keys' -SDL CLI commands 17/7017/4
authorPetri Ovaska <petri.ovaska@nokia.com>
Tue, 9 Nov 2021 06:28:48 +0000 (08:28 +0200)
committerPetri Ovaska <petri.ovaska@nokia.com>
Mon, 15 Nov 2021 10:34:27 +0000 (12:34 +0200)
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 <namespace> [pattern|default '*'] [flags]

Issue-Id: RIC-113
Change-Id: I86b81ff8d8d2cdc0e959e285b19f93696ebe377e
Signed-off-by: Petri Ovaska <petri.ovaska@nokia.com>
12 files changed:
cmd/sdlcli/main.go
internal/cli/cli_private_fn_test.go
internal/cli/get.go [new file with mode: 0644]
internal/cli/get_test.go [new file with mode: 0644]
internal/cli/healthcheck.go
internal/cli/healthcheck_test.go
internal/cli/keys.go [new file with mode: 0644]
internal/cli/keys_test.go [new file with mode: 0644]
internal/cli/root.go
internal/cli/types.go
internal/cli/utils.go
internal/mocks/db_mocks_private_testing.go

index 8d12d1c..aec5e83 100644 (file)
 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()
 }
index 831b5f9..78c6102 100644 (file)
 
 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 (file)
index 0000000..5577663
--- /dev/null
@@ -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 (file)
index 0000000..759ab11
--- /dev/null
@@ -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)
+       }
+}
index aca1fda..2da97a3 100644 (file)
 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 {
index e1de269..2cc0619 100644 (file)
@@ -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 (file)
index 0000000..33dce25
--- /dev/null
@@ -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 <namespace> [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 (file)
index 0000000..bde5c24
--- /dev/null
@@ -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 <namespace> [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 <namespace> [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)
+}
index 6d20ebc..c5cf5ac 100644 (file)
 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)
+       }
 }
index de99e62..5321f0c 100644 (file)
@@ -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"
index 3702050..87aa8ec 100644 (file)
 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)
        }
index 7832ee3..f6d0791 100644 (file)
@@ -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)
+}