Implement SDL CLI 'get namespaces' -command 03/7103/2
authorTimo Tietavainen <timo.tietavainen@nokia.com>
Tue, 23 Nov 2021 06:32:04 +0000 (08:32 +0200)
committerTimo Tietavainen <timo.tietavainen@nokia.com>
Tue, 23 Nov 2021 06:52:36 +0000 (08:52 +0200)
Implement a new 'get namespaces' -command for 'sdlcli' -tool. With this
command user can list all the namespaces found in database. Command
syntax is:
  sdlcli get namespaces [flags]
Supported flags are -g, --group, -h and --help flags. With -g,--group
flag user can get a list of namespace per SDL cluster group address and
-h, --help flags shows the command help.

Issue-Id: RIC-113

Signed-off-by: Timo Tietavainen <timo.tietavainen@nokia.com>
Change-Id: If4d66497eeec94d479748c81cf0898c2fbc78364

internal/cli/cli_private_fn_test.go
internal/cli/get.go
internal/cli/get_test.go
internal/cli/namespaces.go [new file with mode: 0644]
internal/cli/namespaces_test.go [new file with mode: 0644]
internal/cli/types.go
internal/mocks/db_mocks_private_testing.go

index 2dacf4d..d37cb9e 100644 (file)
 package cli
 
 var (
-       NewRootCmd          = newRootCmd
-       NewHealthCheckCmd   = newHealthCheckCmd
-       NewKeysCmdForTest   = newKeysCmd
-       NewGetCmdForTest    = newGetCmd
-       NewSetCmdForTest    = newSetCmd
-       NewRemoveCmdForTest = newRemoveCmd
+       NewRootCmd              = newRootCmd
+       NewHealthCheckCmd       = newHealthCheckCmd
+       NewKeysCmdForTest       = newKeysCmd
+       NewGetCmdForTest        = newGetCmd
+       NewSetCmdForTest        = newSetCmd
+       NewRemoveCmdForTest     = newRemoveCmd
+       NewNamespacesCmdForTest = newNamespacesCmd
 )
index de4f80c..f22ae73 100644 (file)
@@ -43,16 +43,19 @@ func init() {
 var (
        getLong = `Display one or many resources.
 
-Prints keys and keys data in the given namespace.`
+Prints namespaces, keys or keys data in the given namespace.`
 
-       getExample = `  # Get reads keys data in the given namespace.
-  sdlcli get sdlns key1
-
-  # Get reads multiple keys data in the given namespace.
-  sdlcli get sdlns key1 key2 key3
+       getExample = `  # List all the namespaces in database.
+  sdlcli get namespaces
 
   # List keys in the given namespace.
-  sdlcli get keys sdlns`
+  sdlcli get keys sdlns
+
+  # Reads key data in the given namespace.
+  sdlcli get sdlns key1
+
+  # Read multiple keys data in the given namespace.
+  sdlcli get sdlns key1 key2 key3`
 )
 
 func newGetCmd(sdlCb SyncStorageCreateCb) *cobra.Command {
index 22301b9..66bc115 100644 (file)
@@ -72,7 +72,7 @@ func runGetCmdCli() (string, string, error) {
 
 func TestGetCmdShowHelp(t *testing.T) {
        var expOkErr error
-       expHelp := "Display one or many resources.\n\nPrints keys and keys data in the given namespace."
+       expHelp := "Display one or many resources.\n\nPrints namespaces, keys or keys data in the given namespace."
        expHelpUsage := "Usage:\n  get <namespace> <key> [<key2> <key3>... <keyN>] [flags]"
        expArgsErr := errors.New("accepts command or arguments, received 0")
        expNokErr := errors.New("unknown flag: --ff")
diff --git a/internal/cli/namespaces.go b/internal/cli/namespaces.go
new file mode 100644 (file)
index 0000000..9cf5ee8
--- /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
+
+import (
+       "fmt"
+       "github.com/spf13/cobra"
+       "os"
+       "sort"
+       "strings"
+)
+
+func init() {
+       getCmd.AddCommand(newNamespacesCmd(newDatabase))
+}
+
+func newNamespacesCmd(dbCreateCb DbCreateCb) *cobra.Command {
+       cmd := &cobra.Command{
+               Use:   "namespaces",
+               Short: "List all the namespaces in database",
+               Long:  "List all the namespaces in database",
+               Args:  cobra.ExactArgs(0),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       showPerDb, _ := cmd.Flags().GetBool("group")
+                       nsMap, err := runNamespaces(dbCreateCb)
+                       if err != nil {
+                               cmd.PrintErrf("%s\n", buf.String())
+                               return err
+                       }
+                       if showPerDb {
+                               printNamespacesPerDb(cmd, nsMap)
+                       } else {
+                               printNamespaces(cmd, nsMap)
+                       }
+                       return err
+               },
+       }
+       cmd.SetOut(os.Stdout)
+       cmd.Flags().BoolP("group", "g", false, "Show namespaces per SDL DB cluster group")
+       return cmd
+}
+
+func runNamespaces(dbCreateCb DbCreateCb) (map[string][]string, error) {
+       nsMap := make(map[string][]string)
+       for _, dbInst := range dbCreateCb().Instances {
+               keys, err := dbInst.Keys("*")
+               if err != nil {
+                       return nil, err
+               }
+               id, err := getServiceAddress(dbInst)
+               if err != nil {
+                       return nil, err
+               }
+               for _, key := range keys {
+                       namespace, err := parseKeyNamespace(key)
+                       if err != nil {
+                               return nil, err
+                       }
+                       if isUniqueNamespace(nsMap[id], namespace) {
+                               nsMap[id] = append(nsMap[id], namespace)
+                       }
+               }
+       }
+       return nsMap, nil
+}
+
+func getServiceAddress(db iDatabase) (string, error) {
+       state, err := db.State()
+       if err != nil {
+               return "", err
+       }
+       return state.MasterDbState.GetAddress(), nil
+}
+
+func parseKeyNamespace(key string) (string, error) {
+       sIndex := strings.Index(key, "{")
+       if sIndex == -1 {
+               return "", fmt.Errorf("Namespace parsing error, no '{' in key string '%s'", key)
+       }
+       str := key[sIndex+len("{"):]
+       eIndex := strings.Index(str, "}")
+       if eIndex == -1 {
+               return "", fmt.Errorf("Namespace parsing error, no '}' in key string '%s'", key)
+       }
+       return str[:eIndex], nil
+}
+
+func isUniqueNamespace(namespaces []string, newNs string) bool {
+       for _, n := range namespaces {
+               if n == newNs {
+                       return false
+               }
+       }
+       return true
+}
+
+func printNamespaces(cmd *cobra.Command, nsMap map[string][]string) {
+       var namespaces []string
+       for _, nsList := range nsMap {
+               namespaces = append(namespaces, nsList...)
+       }
+
+       sort.Strings(namespaces)
+       for _, ns := range namespaces {
+               cmd.Println(ns)
+       }
+}
+
+func printNamespacesPerDb(cmd *cobra.Command, nsMap map[string][]string) {
+       for addr, nsList := range nsMap {
+               sort.Strings(nsList)
+               for _, ns := range nsList {
+                       if addr == "" {
+                               cmd.Printf("%s\n", ns)
+                       } else {
+                               cmd.Printf("%s: %s\n", addr, ns)
+                       }
+               }
+       }
+}
diff --git a/internal/cli/namespaces_test.go b/internal/cli/namespaces_test.go
new file mode 100644 (file)
index 0000000..28b6343
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+   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"
+       "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+var mNs *nsMock
+
+type nsMock struct {
+       dbIface    *mocks.MockDB
+       dbKeys     []string
+       dbState    sdlgoredis.DbState
+       dbKeysErr  error
+       dbStateErr error
+}
+
+func setupNamespacesCliMock(keys []string, addr string, keysErr, stateErr error) {
+       mNs = new(nsMock)
+       mNs.dbKeys = keys
+       mNs.dbState.MasterDbState.Fields.Role = "Master"
+       mNs.dbState.MasterDbState.Fields.Ip = addr
+       if addr != "" {
+               mNs.dbState.MasterDbState.Fields.Port = "6379"
+       }
+       mNs.dbKeysErr = keysErr
+       mNs.dbStateErr = stateErr
+}
+
+func newNsMockDatabase() *cli.Database {
+       db := &cli.Database{}
+       mNs.dbIface = new(mocks.MockDB)
+       mNs.dbIface.On("Keys", "*").Return(mNs.dbKeys, mNs.dbKeysErr)
+       mNs.dbIface.On("State").Return(&mNs.dbState, mNs.dbStateErr).Maybe()
+       db.Instances = append(db.Instances, mNs.dbIface)
+       return db
+}
+
+func TestNamespacesCmdShowHelp(t *testing.T) {
+       var expOkErr error
+       expNokErrTooManyArgs := errors.New("accepts 0 arg(s), received 1")
+       expHelp := "Usage:\n  namespaces [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{"extra-arg"}, expErr: expNokErrTooManyArgs, expOut: expHelp},
+       }
+
+       for _, test := range tests {
+               buf := new(bytes.Buffer)
+               cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+               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 TestNamespacesCmdSuccess(t *testing.T) {
+       expOut := "ns1\n" + "ns2\n" + "ns3\n"
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{
+               "{ns1},key1",
+               "{ns3},key1",
+               "{ns2},key1",
+               "{ns1},key2",
+       }, "1.2.3.4", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, result)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdWithPerDbFlagSuccess(t *testing.T) {
+       expOut := "1.2.3.4:6379: ns1\n" + "1.2.3.4:6379: ns2\n" + "1.2.3.4:6379: ns3\n"
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{
+               "{ns1},key1",
+               "{ns3},key1",
+               "{ns2},key1",
+               "{ns1},key2",
+       }, "1.2.3.4", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+       cmd.SetArgs([]string{"--group"})
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, result)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdNoKeysInDbSuccess(t *testing.T) {
+       expOut := ""
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{}, "1.2.3.4", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, result)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdWithPerDbFlagNoKeysInDbSuccess(t *testing.T) {
+       expOut := ""
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{}, "1.2.3.4", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+       cmd.SetArgs([]string{"--group"})
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, result)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdWithPerDbFlagStandaloneRedisAddressMissingSuccess(t *testing.T) {
+       expOut := "ns1\n" + "ns2\n" + "ns3\n"
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{
+               "{ns1},key1",
+               "{ns3},key1",
+               "{ns2},key1",
+               "{ns1},key2",
+       }, "", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+       cmd.SetArgs([]string{"--group"})
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, result)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdDbKeysFailure(t *testing.T) {
+       expNokErr := errors.New("Some error")
+       expOut := "Error: Some error"
+
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock(nil, "1.2.3.4", expNokErr, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Equal(t, expNokErr, err)
+       assert.Contains(t, result, expOut)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdDbStateFailure(t *testing.T) {
+       expNokErr := errors.New("Some error")
+       expOut := "Error: Some error"
+
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock(nil, "1.2.3.4", nil, expNokErr)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Equal(t, expNokErr, err)
+       assert.Contains(t, result, expOut)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdNsStartMarkerFailure(t *testing.T) {
+       expNokErr := errors.New("Namespace parsing error, no '{' in key string 'ns2},key1'")
+       expOut := "Error: Namespace parsing error, no '{' in key string 'ns2},key1'"
+
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{
+               "{ns1},key1",
+               "ns2},key1",
+               "{ns1},key2",
+       }, "1.2.3.4", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Equal(t, expNokErr, err)
+       assert.Contains(t, result, expOut)
+       mNs.dbIface.AssertExpectations(t)
+}
+
+func TestNamespacesCmdNsEndMarkerFailure(t *testing.T) {
+       expNokErr := errors.New("Namespace parsing error, no '}' in key string '{ns2,key1'")
+       expOut := "Error: Namespace parsing error, no '}' in key string '{ns2,key1'"
+
+       buf := new(bytes.Buffer)
+       setupNamespacesCliMock([]string{
+               "{ns1},key1",
+               "{ns2,key1",
+               "{ns1},key2",
+       }, "1.2.3.4", nil, nil)
+       cmd := cli.NewNamespacesCmdForTest(newNsMockDatabase)
+       cmd.SetOut(buf)
+       cmd.SetErr(buf)
+
+       err := cmd.Execute()
+       result := buf.String()
+
+       assert.Equal(t, expNokErr, err)
+       assert.Contains(t, result, expOut)
+       mNs.dbIface.AssertExpectations(t)
+}
index 53953a8..f5c4439 100644 (file)
@@ -29,6 +29,7 @@ import "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis"
 type iDatabase interface {
        Info() (*sdlgoredis.DbInfo, error)
        State() (*sdlgoredis.DbState, error)
+       Keys(pattern string) ([]string, error)
 }
 
 //Database struct is a holder for the internal database instances.
index a17f7d3..42bb061 100644 (file)
@@ -41,6 +41,11 @@ func (m *MockDB) State() (*sdlgoredis.DbState, error) {
        return a.Get(0).(*sdlgoredis.DbState), a.Error(1)
 }
 
+func (m *MockDB) Keys(pattern string) ([]string, error) {
+       a := m.Called(pattern)
+       return a.Get(0).([]string), a.Error(1)
+}
+
 type MockSdlApi struct {
        mock.Mock
 }