diff --git a/sonic-chassisd/scripts/chassisd b/sonic-chassisd/scripts/chassisd index 99ddb0643..ff023e80e 100755 --- a/sonic-chassisd/scripts/chassisd +++ b/sonic-chassisd/scripts/chassisd @@ -790,21 +790,74 @@ class ChassisdDaemon(daemon_base.DaemonBase): self.log_info("Shutting down...") +class DpuStateManagerTask(ProcessTaskBase): + + def __init__(self, log_identifier, dpu_state_updater): + super(DpuStateManagerTask, self).__init__() + + self.logger = logger.Logger(log_identifier) + self.dpu_state_updater = dpu_state_updater + self.state_db = daemon_base.db_connect('STATE_DB') + self.app_db = daemon_base.db_connect('APPL_DB') + + def task_worker(self): + sel = swsscommon.Select() + selectable = [ + swsscommon.SubscriberStateTable(self.app_db, 'PORT_TABLE'), + swsscommon.SubscriberStateTable(self.state_db, 'SYSTEM_READY') + ] + + for s in selectable: + sel.addSelectable(s) + + try: + while True: + (state, c) = sel.select(SELECT_TIMEOUT) + + if state == swsscommon.Select.TIMEOUT: + continue + + if state != swsscommon.Select.OBJECT: + continue + + for s in selectable: + s.pops() + + self.dpu_state_updater.update_state() + + except KeyboardInterrupt: + pass + + class DpuChassisdDaemon(ChassisdDaemon): def run(self): self.log_info("Starting up...") + poll_dpu_state = True + if not try_get(self.platform_chassis.get_dataplane_state, default=None) and not \ + try_get(self.platform_chassis.get_controlplane_state, default=None): + poll_dpu_state = False + dpu_updater = DpuStateUpdater(SYSLOG_IDENTIFIER, self.platform_chassis) + dpu_state_mng = None + + if not poll_dpu_state: + dpu_state_mng = DpuStateManagerTask(SYSLOG_IDENTIFIER, dpu_updater) + dpu_state_mng.task_run() # Start main loop self.log_info("Start daemon main loop") while not self.stop.wait(CHASSIS_INFO_UPDATE_PERIOD_SECS): - dpu_updater.update_state() + if poll_dpu_state: + dpu_updater.update_state() self.log_info("Stop daemon main loop") + if dpu_state_mng: + dpu_state_mng.task_stop() + dpu_updater.deinit() self.log_info("Shutting down...") diff --git a/sonic-chassisd/tests/mock_swsscommon.py b/sonic-chassisd/tests/mock_swsscommon.py index 4872d6ce9..0c3d8419c 100644 --- a/sonic-chassisd/tests/mock_swsscommon.py +++ b/sonic-chassisd/tests/mock_swsscommon.py @@ -54,6 +54,7 @@ def __init__(self, fvs): pass class Select: + OBJECT = 0 TIMEOUT = 1 def addSelectable(self, selectable): @@ -66,7 +67,12 @@ def select(self, timeout=-1, interrupt_on_signal=False): return self.TIMEOUT, None class SubscriberStateTable(Table): - pass + + def pop(self): + return None + + def pops(self): + return None class RedisPipeline: def __init__(self, db): diff --git a/sonic-chassisd/tests/test_dpu_chassisd.py b/sonic-chassisd/tests/test_dpu_chassisd.py index 4f26f1659..09a9ef4ea 100644 --- a/sonic-chassisd/tests/test_dpu_chassisd.py +++ b/sonic-chassisd/tests/test_dpu_chassisd.py @@ -116,6 +116,52 @@ def hset(key, field, value): 'dpu_control_plane_state': 'down', 'dpu_control_plane_time': '2000-01-01 00:00:00'}} +@pytest.mark.parametrize('dpu_id, dp_state, cp_state, expected_state', [ + (0, False, False, {'DPU0': + {'dpu_data_plane_state': 'down', 'dpu_data_plane_time': '2000-01-01 00:00:00', + 'dpu_control_plane_state': 'down', 'dpu_control_plane_time': '2000-01-01 00:00:00'}}), + (0, False, True, {'DPU0': + {'dpu_data_plane_state': 'down', 'dpu_data_plane_time': '2000-01-01 00:00:00', + 'dpu_control_plane_state': 'up', 'dpu_control_plane_time': '2000-01-01 00:00:00'}}), + (0, True, True, {'DPU0': + {'dpu_data_plane_state': 'up', 'dpu_data_plane_time': '2000-01-01 00:00:00', + 'dpu_control_plane_state': 'up', 'dpu_control_plane_time': '2000-01-01 00:00:00'}}), +]) +def test_dpu_state_manager(dpu_id, dp_state, cp_state, expected_state): + chassis = MockDpuChassis() + + chassis.get_dpu_id = MagicMock(return_value=dpu_id) + chassis.get_dataplane_state = MagicMock(return_value=dp_state) + chassis.get_controlplane_state = MagicMock(return_value=cp_state) + + chassis_state_db = {} + + def hset(key, field, value): + print(key, field, value) + if key not in chassis_state_db: + chassis_state_db[key] = {} + + chassis_state_db[key][field] = value + + with mock.patch.object(swsscommon.Table, 'hset', side_effect=hset): + with mock.patch.object(swsscommon.Select, 'select', side_effect=((swsscommon.Select.OBJECT, None), (swsscommon.Select.OBJECT, None), KeyboardInterrupt)): + dpu_updater = DpuStateUpdater(SYSLOG_IDENTIFIER, chassis) + dpu_updater._time_now = MagicMock(return_value='2000-01-01 00:00:00') + + dpu_state_mng = DpuStateManagerTask(SYSLOG_IDENTIFIER, dpu_updater) + + dpu_state_mng.task_worker() + + assert chassis_state_db == expected_state + + dpu_updater.deinit() + + # After the deinit we assume that the DPU state is down. + assert chassis_state_db == {'DPU0': + {'dpu_data_plane_state': 'down', 'dpu_data_plane_time': '2000-01-01 00:00:00', + 'dpu_control_plane_state': 'down', 'dpu_control_plane_time': '2000-01-01 00:00:00'}} + + def test_dpu_chassis_daemon(): # Test the chassisd run chassis = MockDpuChassis()