diff --git a/gnmi_server/server_test.go b/gnmi_server/server_test.go index 3c27258f..8a0df8b5 100644 --- a/gnmi_server/server_test.go +++ b/gnmi_server/server_test.go @@ -4281,7 +4281,7 @@ func TestGNMINative(t *testing.T) { // This test is used for single database configuration // Run tests not marked with multidb - cmd := exec.Command("bash", "-c", "cd "+path+" && "+"pytest -m 'not multidb'") + cmd := exec.Command("bash", "-c", "cd "+path+" && "+"pytest -m 'not multidb and not multins'") if result, err := cmd.Output(); err != nil { fmt.Println(string(result)) t.Errorf("Fail to execute pytest: %v", err) @@ -4341,6 +4341,55 @@ func TestGNMINativeMultiDB(t *testing.T) { } } +// Test configuration with multiple namespaces +func TestGNMINativeMultiNamespace(t *testing.T) { + mock1 := gomonkey.ApplyFunc(dbus.SystemBus, func() (conn *dbus.Conn, err error) { + return &dbus.Conn{}, nil + }) + defer mock1.Reset() + mock2 := gomonkey.ApplyMethod(reflect.TypeOf(&dbus.Object{}), "Go", func(obj *dbus.Object, method string, flags dbus.Flags, ch chan *dbus.Call, args ...interface{}) *dbus.Call { + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(0) + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + sdcfg.Init() + err := test_utils.SetupMultiNamespace() + if err != nil { + t.Fatalf("error Setting up MultiNamespace files with err %T", err) + } + + /* https://www.gopherguides.com/articles/test-cleanup-in-go-1-14*/ + t.Cleanup(func() { + if err := test_utils.CleanUpMultiNamespace(); err != nil { + t.Fatalf("error Cleaning up MultiNamespace files with err %T", err) + + } + }) + + s := createServer(t, 8080) + go runServer(t, s) + defer s.Stop() + ns, _ := sdcfg.GetDbDefaultNamespace() + initFullConfigDb(t, ns) + + path, _ := os.Getwd() + path = filepath.Dir(path) + + // This test is used for multiple namespaces configuration + // Run tests marked with multins + cmd := exec.Command("bash", "-c", "cd "+path+" && "+"pytest -m 'multins'") + if result, err := cmd.Output(); err != nil { + fmt.Println(string(result)) + t.Errorf("Fail to execute pytest: %v", err) + } else { + fmt.Println(string(result)) + } +} + func TestServerPort(t *testing.T) { s := createServer(t, -8080) port := s.Port() diff --git a/pytest.ini b/pytest.ini index 1bbd884b..355fa5f2 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] markers: multidb + multins diff --git a/sonic_data_client/mixed_db_client.go b/sonic_data_client/mixed_db_client.go index 4fe1bb22..57c78f83 100644 --- a/sonic_data_client/mixed_db_client.go +++ b/sonic_data_client/mixed_db_client.go @@ -47,6 +47,7 @@ const ELEM_INDEX_INSTANCE = 1 const UPDATE_OPERATION = "add" const DELETE_OPERATION = "remove" const REPLACE_OPERATION = "replace" +const HOSTNAME = "localhost" const ( opAdd = iota @@ -389,7 +390,7 @@ func (c *MixedDbClient) ParseDatabase(prefix *gnmipb.Path, paths []*gnmipb.Path) if c.namespace_cnt > 1 && c.container_cnt > 1 { // Support smartswitch with multiple asic NPU // The elelement can be "localhost", "asic0", "asic1", ..., "dpu0", "dpu1", ... - if elem_name != "localhost" { + if elem_name != HOSTNAME { // Try namespace dbkey1, ok := sdcfg.GetDbInstanceFromTarget(elem_name, sdcfg.SONIC_DEFAULT_CONTAINER) if ok { @@ -419,11 +420,11 @@ func (c *MixedDbClient) ParseDatabase(prefix *gnmipb.Path, paths []*gnmipb.Path) return "", nil, status.Error(codes.Unimplemented, "No target specified in path") } // GNMI path uses localhost as default namespace - if namespace == "localhost" { + if namespace == HOSTNAME { namespace = sdcfg.SONIC_DEFAULT_NAMESPACE } // GNMI path uses localhost as default container - if container == "localhost" { + if container == HOSTNAME { container = sdcfg.SONIC_DEFAULT_NAMESPACE } dbkey := swsscommon.NewSonicDBKey() @@ -1227,6 +1228,16 @@ func (c *MixedDbClient) SetIncrementalConfig(delete []*gnmipb.Path, replace []*g return err } + multiNs, err := sdcfg.CheckDbMultiNamespace() + if err != nil { + return err + } + namespace := c.dbkey.GetNetns() + // Default name space for GCU is localhost + if namespace == sdcfg.SONIC_DEFAULT_NAMESPACE { + namespace = HOSTNAME + } + var patchList [](map[string]interface{}) /* DELETE */ for _, path := range delete { @@ -1255,6 +1266,9 @@ func (c *MixedDbClient) SetIncrementalConfig(delete []*gnmipb.Path, replace []*g if err != nil { return err } + if multiNs { + curr["path"] = "/" + namespace + curr["path"].(string) + } patchList = append(patchList, curr) } @@ -1294,6 +1308,9 @@ func (c *MixedDbClient) SetIncrementalConfig(delete []*gnmipb.Path, replace []*g if err != nil { return err } + if multiNs { + curr["path"] = "/" + namespace + curr["path"].(string) + } patchList = append(patchList, curr) } @@ -1329,6 +1346,9 @@ func (c *MixedDbClient) SetIncrementalConfig(delete []*gnmipb.Path, replace []*g if err != nil { return err } + if multiNs { + curr["path"] = "/" + namespace + curr["path"].(string) + } patchList = append(patchList, curr) } if len(patchList) == 0 { diff --git a/test/test_gnmi_configdb_patch.py b/test/test_gnmi_configdb_patch.py index 9195d187..9b13b719 100644 --- a/test/test_gnmi_configdb_patch.py +++ b/test/test_gnmi_configdb_patch.py @@ -3122,6 +3122,48 @@ def common_test_handler(self, test_data): diff = jsonpatch.make_patch(result, test_data["target_json"]) assert len(diff.patch) == 0, "%s failed, generated json: %s" % (test_data["test_name"], str(result)) + def multins_test_handler(self, test_data): + ''' + Common code for multins test + ''' + if os.path.exists(patch_file): + os.system("rm " + patch_file) + create_checkpoint(checkpoint_file, json.dumps(test_data['origin_json'])) + update_list = [] + replace_list = [] + delete_list = [] + for i, data in enumerate(test_data["operations"]): + path = data["path"] + if data['op'] == "update": + value = json.dumps(data["value"]) + file_name = "update" + str(i) + file_object = open(file_name, "w") + file_object.write(value) + file_object.close() + update_list.append(path + ":@./" + file_name) + elif data['op'] == "replace": + value = json.dumps(data["value"]) + file_name = "replace" + str(i) + file_object = open(file_name, "w") + file_object.write(value) + file_object.close() + replace_list.append(path + ":@./" + file_name) + elif data['op'] == "del": + delete_list.append(path) + else: + pytest.fail("Invalid operation: %s" % data['op']) + + # Send GNMI request + ret, msg = gnmi_set(delete_list, update_list, replace_list) + assert ret == 0, msg + assert os.path.exists(patch_file), "No patch file" + with open(patch_file,"r") as pf: + patch_json = json.load(pf) + for patch in patch_json: + if "path" in patch: + path = patch["path"] + assert path.startswith("/localhost"), "Invalid path: %s" % path + @pytest.mark.parametrize("test_data", test_data_aaa_patch) def test_gnmi_aaa_patch(self, test_data): ''' @@ -3129,6 +3171,14 @@ def test_gnmi_aaa_patch(self, test_data): ''' self.common_test_handler(test_data) + @pytest.mark.multins + @pytest.mark.parametrize("test_data", test_data_aaa_patch) + def test_gnmi_aaa_patch(self, test_data): + ''' + Generate GNMI request for AAA and verify jsonpatch + ''' + self.multins_test_handler(test_data) + @pytest.mark.parametrize("test_data", test_data_bgp_prefix_patch) def test_gnmi_bgp_prefix_patch(self, test_data): '''