diff --git a/.gitignore b/.gitignore index d70506f2..3c42991f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,26 @@ +# Build directory build/ + +# Debian packaging files debian/.debhelper/ debian/files debian/sonic-telemetry.debhelper.log debian/sonic-telemetry.substvars debian/sonic-telemetry/ -vendor -src -cvl -translib +debian/sonic-gnmi.debhelper.log +debian/sonic-gnmi.substvars +debian/sonic-gnmi/ + +# Vendor directory +vendor/ + +# Source directories +src/ +cvl/ +translib/ + +# SWSS common generated files +swsscommon/swsscommon.go +swsscommon/swsscommon.i +swsscommon/swsscommon_wrap.cxx +swsscommon/swsscommon_wrap.h \ No newline at end of file diff --git a/common_utils/context.go b/common_utils/context.go index a9059f4a..09bff486 100644 --- a/common_utils/context.go +++ b/common_utils/context.go @@ -54,6 +54,7 @@ const ( DBUS_STOP_SERVICE DBUS_RESTART_SERVICE DBUS_FILE_STAT + DBUS_HALT_SYSTEM COUNTER_SIZE ) @@ -91,6 +92,8 @@ func (c CounterType) String() string { return "DBUS restart service" case DBUS_FILE_STAT: return "DBUS file stat" + case DBUS_HALT_SYSTEM: + return "DBUS halt system" default: return "" } diff --git a/gnmi_server/gnoi.go b/gnmi_server/gnoi.go index f11a9a81..4c3aad6c 100644 --- a/gnmi_server/gnoi.go +++ b/gnmi_server/gnoi.go @@ -139,6 +139,20 @@ func (srv *SystemServer) KillProcess(ctx context.Context, req *gnoi_system_pb.Ki return &resp, nil } +func HaltSystem() error { + sc,err := ssc.NewDbusClient() + if err != nil { + return err + } + + log.V(2).Infof("Halting the system..") + err = sc.HaltSystem() + if err != nil { + log.V(2).Infof("Failed to Halt the system %v", err); + } + return err +} + func RebootSystem(fileName string) error { log.V(2).Infof("Rebooting with %s...", fileName) sc, err := ssc.NewDbusClient() @@ -158,18 +172,30 @@ func (srv *SystemServer) Reboot(ctx context.Context, req *gnoi_system_pb.RebootR } log.V(1).Info("gNOI: Reboot") log.V(1).Info("Request:", req) - log.V(1).Info("Reboot system now, delay is ignored...") - // TODO: Support GNOI reboot delay - // Delay in nanoseconds before issuing reboot. - // https://github.com/openconfig/gnoi/blob/master/system/system.proto#L102-L115 - config_db_json, err := io.ReadFile(fileName) - if errors.Is(err, os.ErrNotExist) { - fileName = "" - } - err = RebootSystem(string(config_db_json)) - if err != nil { - return nil, err + + // Check the reboot type + switch req.GetMethod() { + case gnoi_system_pb.RebootMethod_HALT: + log.V(1).Info("Reboot method is HALT. Halting the system...") + err = HaltSystem() + if err != nil { + return nil, err + } + default: + log.V(1).Info("Reboot system now, delay is ignored...") + // TODO: Support GNOI reboot delay + // Delay in nanoseconds before issuing reboot. + // https://github.com/openconfig/gnoi/blob/master/system/system.proto#L102-L115 + config_db_json, err := io.ReadFile(fileName) + if errors.Is(err, os.ErrNotExist) { + fileName = "" + } + err = RebootSystem(string(config_db_json)) + if err != nil { + return nil, err + } } + var resp gnoi_system_pb.RebootResponse return &resp, nil } diff --git a/gnmi_server/server_test.go b/gnmi_server/server_test.go index 8c7e00fe..40a90abc 100644 --- a/gnmi_server/server_test.go +++ b/gnmi_server/server_test.go @@ -1297,11 +1297,11 @@ func runGnmiTestGet(t *testing.T, namespace string) { }, { desc: "Get valid but non-existing node", - pathTarget: "COUNTERS_DB", + pathTarget: stateDBPath, textPbPath: ` - elem: - `, - wantRetCode: codes.NotFound, + elem: + `, + wantRetCode: codes.OK, }, { desc: "Get COUNTERS_PORT_NAME_MAP", pathTarget: "COUNTERS_DB", @@ -3487,6 +3487,191 @@ func TestClientConnections(t *testing.T) { } } +func TestWildcardTableNoError(t *testing.T) { + s := createServer(t, 8081) + go runServer(t, s) + defer s.ForceStop() + + fileName := "../testdata/NEIGH_STATE_TABLE_MAP.txt" + neighStateTableByte, err := ioutil.ReadFile(fileName) + if err != nil { + t.Fatalf("read file %v err: %v", fileName, err) + } + + var neighStateTableJson interface{} + json.Unmarshal(neighStateTableByte, &neighStateTableJson) + + tests := []struct { + desc string + q client.Query + wantNoti []client.Notification + poll int + }{ + { + desc: "poll query for NEIGH_STATE_TABLE", + poll: 1, + q: client.Query{ + Target: "STATE_DB", + Type: client.Poll, + Queries: []client.Path{{"NEIGH_STATE_TABLE"}}, + TLS: &tls.Config{InsecureSkipVerify: true}, + }, + wantNoti: []client.Notification{ + client.Update{Path: []string{"NEIGH_STATE_TABLE"}, TS: time.Unix(0, 200), Val: neighStateTableJson}, + client.Update{Path: []string{"NEIGH_STATE_TABLE"}, TS: time.Unix(0, 200), Val: neighStateTableJson}, + }, + }, + } + namespace, _ := sdcfg.GetDbDefaultNamespace() + prepareStateDb(t, namespace) + var mutexNoti sync.Mutex + for _, tt := range tests { + + t.Run(tt.desc, func(t *testing.T) { + q := tt.q + q.Addrs = []string{"127.0.0.1:8081"} + c := client.New() + var gotNoti []client.Notification + q.NotificationHandler = func(n client.Notification) error { + mutexNoti.Lock() + if nn, ok := n.(client.Update); ok { + nn.TS = time.Unix(0, 200) + gotNoti = append(gotNoti, nn) + } + mutexNoti.Unlock() + return nil + } + + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + defer wg.Done() + if err := c.Subscribe(context.Background(), q); err != nil { + t.Errorf("c.Subscribe(): got error %v, expected nil", err) + } + }() + + wg.Wait() + + for i := 0; i < tt.poll; i++ { + if err := c.Poll(); err != nil { + t.Errorf("c.Poll(): got error %v, expected nil", err) + } + } + + mutexNoti.Lock() + + if len(gotNoti) == 0 { + t.Errorf("expected non zero notifications") + } + + if diff := pretty.Compare(tt.wantNoti, gotNoti); diff != "" { + t.Log("\n Want: \n", tt.wantNoti) + t.Log("\n Got: \n", gotNoti) + t.Errorf("unexpected updates: \n%s", diff) + } + + mutexNoti.Unlock() + + c.Close() + }) + } +} + +func TestNonExistentTableNoError(t *testing.T) { + s := createServer(t, 8081) + go runServer(t, s) + defer s.ForceStop() + + fileName := "../testdata/EMPTY_JSON.txt" + transceiverDomSensorTableByte, err := ioutil.ReadFile(fileName) + if err != nil { + t.Fatalf("read file %v err: %v", fileName, err) + } + + var transceiverDomSensorTableJson interface{} + json.Unmarshal(transceiverDomSensorTableByte, &transceiverDomSensorTableJson) + + tests := []struct { + desc string + q client.Query + wantNoti []client.Notification + poll int + }{ + { + desc: "poll query for TRANSCEIVER_DOM_SENSOR", + poll: 1, + q: client.Query{ + Target: "STATE_DB", + Type: client.Poll, + Queries: []client.Path{{"TRANSCEIVER_DOM_SENSOR"}}, + TLS: &tls.Config{InsecureSkipVerify: true}, + }, + wantNoti: []client.Notification{ + client.Update{Path: []string{"TRANSCEIVER_DOM_SENSOR"}, TS: time.Unix(0, 200), Val: transceiverDomSensorTableJson}, + client.Update{Path: []string{"TRANSCEIVER_DOM_SENSOR"}, TS: time.Unix(0, 200), Val: transceiverDomSensorTableJson}, + }, + }, + } + namespace, _ := sdcfg.GetDbDefaultNamespace() + prepareStateDb(t, namespace) + var mutexNoti sync.Mutex + + for _, tt := range tests { + prepareStateDb(t, namespace) + t.Run(tt.desc, func(t *testing.T) { + q := tt.q + q.Addrs = []string{"127.0.0.1:8081"} + c := client.New() + var gotNoti []client.Notification + q.NotificationHandler = func(n client.Notification) error { + mutexNoti.Lock() + if nn, ok := n.(client.Update); ok { + nn.TS = time.Unix(0, 200) + gotNoti = append(gotNoti, nn) + } + mutexNoti.Unlock() + return nil + } + + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + defer wg.Done() + if err := c.Subscribe(context.Background(), q); err != nil { + t.Errorf("c.Subscribe(): got error %v, expected nil", err) + } + }() + + wg.Wait() + + for i := 0; i < tt.poll; i++ { + if err := c.Poll(); err != nil { + t.Errorf("c.Poll(): got error %v, expected nil", err) + } + } + + mutexNoti.Lock() + + if len(gotNoti) == 0 { + t.Errorf("expected non zero notifications") + } + + if diff := pretty.Compare(tt.wantNoti, gotNoti); diff != "" { + t.Log("\n Want: \n", tt.wantNoti) + t.Log("\n Got: \n", gotNoti) + t.Errorf("unexpected updates: \n%s", diff) + } + + mutexNoti.Unlock() + + c.Close() + }) + } +} + func TestConnectionDataSet(t *testing.T) { s := createServer(t, 8081) go runServer(t, s) diff --git a/sonic_data_client/db_client.go b/sonic_data_client/db_client.go index 99f58a8a..cb94d1b0 100644 --- a/sonic_data_client/db_client.go +++ b/sonic_data_client/db_client.go @@ -670,11 +670,13 @@ func populateDbtablePath(prefix, path *gnmipb.Path, pathG2S *map[*gnmipb.Path][] // <5> DB Table Key Key Field switch len(stringSlice) { case 2: // only table name provided - res, err := redisDb.Keys(tblPath.tableName + "*").Result() - if err != nil || len(res) < 1 { - log.V(2).Infof("Invalid db table Path %v %v", target, dbPath) - return fmt.Errorf("Failed to find %v %v %v %v", target, dbPath, err, res) + wildcardTableName := tblPath.tableName + "*" + log.V(6).Infof("Fetching all keys for %v with table name %s", target, wildcardTableName) + res, err := redisDb.Keys(wildcardTableName).Result() + if err != nil { + return fmt.Errorf("redis Keys op failed for %v %v, got err %v %v", target, dbPath, err, res) } + log.V(6).Infof("Result of keys operation for %v %v, got %v", target, dbPath, res) tblPath.tableKey = "" case 3: // Third element could be table key; or field name in which case table name itself is the key too n, err := redisDb.Exists(tblPath.tableName + tblPath.delimitor + mappedKey).Result() diff --git a/sonic_service_client/dbus_client.go b/sonic_service_client/dbus_client.go index 48291570..d54dda13 100644 --- a/sonic_service_client/dbus_client.go +++ b/sonic_service_client/dbus_client.go @@ -19,6 +19,7 @@ type Service interface { StopService(service string) error RestartService(service string) error GetFileStat(path string) (map[string]string, error) + HaltSystem() error } type DbusClient struct { @@ -124,7 +125,7 @@ func (c *DbusClient) ApplyPatchYang(patch string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".apply_patch_yang" - _, err := DbusApi(busName, busPath, intName, 240, patch) + _, err := DbusApi(busName, busPath, intName, 600, patch) return err } @@ -134,7 +135,7 @@ func (c *DbusClient) ApplyPatchDb(patch string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".apply_patch_db" - _, err := DbusApi(busName, busPath, intName, 240, patch) + _, err := DbusApi(busName, busPath, intName, 600, patch) return err } @@ -191,3 +192,21 @@ func (c *DbusClient) GetFileStat(path string) (map[string]string, error) { data, _ := result.(map[string]string) return data, nil } + +func (c *DbusClient) HaltSystem() error { + // Increment the counter for the DBUS_HALT_SYSTEM event + common_utils.IncCounter(common_utils.DBUS_HALT_SYSTEM) + + // Set the module name and update the D-Bus properties + modName := "systemd" + busName := c.busNamePrefix + modName + busPath := c.busPathPrefix + modName + intName := c.intNamePrefix + modName + ".execute_reboot" + + //Set the method to HALT(3) the system + const RebootMethod_HALT = 3 + + // Invoke the D-Bus API to execute the halt command + _, err := DbusApi(busName, busPath, intName, 10, RebootMethod_HALT) + return err +} diff --git a/test/test_gnoi.py b/test/test_gnoi.py index 61b7c067..f8ceccf3 100644 --- a/test/test_gnoi.py +++ b/test/test_gnoi.py @@ -21,6 +21,17 @@ def test_gnoi_reboot(self): assert ret == 0, 'Fail to read counter' assert new_cnt == old_cnt+1, 'DBUS API is not invoked' + def test_gnoi_reboot_halt(self): + ret, old_cnt = gnmi_dump('DBUS halt system') + assert ret == 0, 'Fail to read counter' + + ret, msg = gnoi_reboot(3, 0, 'Test halt system') + assert ret == 0, msg + + ret, new_cnt = gnmi_dump('DBUS halt system') + assert ret == 0, 'Fail to read counter' + assert new_cnt == old_cnt+1, 'DBUS API is not invoked' + def test_gnoi_rebootstatus(self): ret, msg = gnoi_rebootstatus() assert ret != 0, 'RebootStatus should fail' + msg @@ -74,4 +85,4 @@ def test_gnoi_restartprocess_invalid(self): ret, new_cnt = gnmi_dump('DBUS restart service') assert ret == 0, 'Fail to read counter' - assert new_cnt == old_cnt, 'DBUS API invoked unexpectedly' \ No newline at end of file + assert new_cnt == old_cnt, 'DBUS API invoked unexpectedly' diff --git a/testdata/EMPTY_JSON.txt b/testdata/EMPTY_JSON.txt new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/testdata/EMPTY_JSON.txt @@ -0,0 +1 @@ +{}