diff --git a/sonic-chassisd/scripts/chassisd b/sonic-chassisd/scripts/chassisd index 545eb766f..5a0565c9f 100755 --- a/sonic-chassisd/scripts/chassisd +++ b/sonic-chassisd/scripts/chassisd @@ -708,7 +708,7 @@ class SmartSwitchModuleUpdater(ModuleUpdater): fvs = dict(fvs[-1]) return fvs[CHASSIS_MODULE_ADMIN_STATUS] else: - return 'down' + return 'empty' def module_db_update(self): for module_index in range(0, self.num_modules): @@ -1261,6 +1261,48 @@ class ChassisdDaemon(daemon_base.DaemonBase): else: self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + def submit_dpu_callback(self, module_index, admin_state): + try_get(self.module_updater.chassis.get_module(module_index).set_admin_state, admin_state, default=False) + pass + + def set_initial_dpu_admin_state(self): + """Send admin_state trigger once to modules those are powered up""" + threads = [] + for module_index in range(0, self.module_updater.num_modules): + op = None + # Get operational state of DPU + module_name = self.platform_chassis.get_module(module_index).get_name() + operational_state = self.platform_chassis.get_module(module_index).get_oper_status() + + try: + # Get admin state of DPU + admin_state = self.module_updater.get_module_admin_status(module_name) + if admin_state == 'empty' and operational_state != ModuleBase.MODULE_STATUS_OFFLINE: + # shutdown DPU + op = MODULE_ADMIN_DOWN + + # Initialize DPU_STATE DB table on bootup + dpu_state_key = "DPU_STATE|" + module_name + if operational_state == ModuleBase.MODULE_STATUS_ONLINE: + op_state = 'up' + else: + op_state = 'down' + self.module_updater.update_dpu_state(dpu_state_key, op_state) + + if op is not None: + # Create and start a thread for the DPU logic + thread = threading.Thread(target=self.submit_dpu_callback, args=(module_index, op)) + thread.daemon = True # Set as a daemon thread + thread.start() + threads.append(thread) + + except Exception as e: + self.log_error(f"Error in run: {str(e)}", exc_info=True) + + # Wait for all threads to finish + for thread in threads: + thread.join() + # Run daemon def run(self): self.log_info("Starting up...") @@ -1296,6 +1338,10 @@ class ChassisdDaemon(daemon_base.DaemonBase): # Start main loop self.log_info("Start daemon main loop") + # Set the initial DPU admin state for SmartSwitch + if self.smartswitch: + self.set_initial_dpu_admin_state() + while not self.stop.wait(CHASSIS_INFO_UPDATE_PERIOD_SECS): self.module_updater.module_db_update() self.module_updater.check_midplane_reachability() diff --git a/sonic-chassisd/tests/test_chassisd.py b/sonic-chassisd/tests/test_chassisd.py index c84bb2576..5fc9de77e 100644 --- a/sonic-chassisd/tests/test_chassisd.py +++ b/sonic-chassisd/tests/test_chassisd.py @@ -1296,6 +1296,185 @@ def test_daemon_run_smartswitch(): with patch.object(module_updater, 'num_modules', 1): daemon_chassisd.run() +def test_set_initial_dpu_admin_state_down(): + # Test the chassisd run + chassis = MockSmartSwitchChassis() + + # DPU0 details + index = 0 + name = "DPU0" + desc = "DPU Module 0" + slot = 0 + sup_slot = 0 + serial = "DPU0-0000" + module_type = ModuleBase.MODULE_TYPE_DPU + module = MockModule(index, name, desc, module_type, slot, serial) + module.set_midplane_ip() + + # Set initial state for DPU0 + status = ModuleBase.MODULE_STATUS_PRESENT + module.set_oper_status(status) + chassis.module_list.append(module) + + # Supervisor ModuleUpdater + module_updater = SmartSwitchModuleUpdater(SYSLOG_IDENTIFIER, chassis) + module_updater.module_db_update() + module_updater.modules_num_update() + + # ChassisdDaemon setup + daemon_chassisd = ChassisdDaemon(SYSLOG_IDENTIFIER, chassis) + daemon_chassisd.module_updater = module_updater + daemon_chassisd.stop = MagicMock() + daemon_chassisd.stop.wait.return_value = True + daemon_chassisd.smartswitch = True + + # Import platform and use chassis as platform_chassis + import sonic_platform.platform + platform_chassis = chassis + + # Mock objects + mock_chassis = MagicMock() + mock_module_updater = MagicMock() + + # Mock the module (DPU0) + mock_module = MagicMock() + mock_module.get_name.return_value = "DPU0" + + # Mock chassis.get_module to return the mock_module for DPU0 + def mock_get_module(index): + if index == 0: # For DPU0 + return mock_module + return None # No other modules available in this test case + + # Apply the side effect for chassis.get_module + mock_chassis.get_module.side_effect = mock_get_module + + # Mock state_db + mock_state_db = MagicMock() + # fvs_mock = [True, {CHASSIS_MIDPLANE_INFO_ACCESS_FIELD: 'True'}] + # mock_state_db.get.return_value = fvs_mock + + # Mock db_connect + mock_db_connect = MagicMock() + mock_db_connect.return_value = mock_state_db + + # Mock admin_status + # mock_module_updater.get_module_admin_status.return_value = 'down' + + # Set access of DPU0 Down + midplane_table = module_updater.midplane_table + module.set_midplane_reachable(True) + module_updater.check_midplane_reachability() + fvs = midplane_table.get(name) + assert fvs != None + if isinstance(fvs, list): + fvs = dict(fvs[-1]) + assert module.get_midplane_ip() == fvs[CHASSIS_MIDPLANE_INFO_IP_FIELD] + assert str(module.is_midplane_reachable()) == fvs[CHASSIS_MIDPLANE_INFO_ACCESS_FIELD] + + # Patching platform's Chassis object to return the mocked module + with patch.object(sonic_platform.platform.Chassis, 'is_smartswitch') as mock_is_smartswitch, \ + patch.object(sonic_platform.platform.Chassis, 'get_module', side_effect=mock_get_module): + + # Simulate that the system is a SmartSwitch + mock_is_smartswitch.return_value = True + + # Patch num_modules for the updater + with patch.object(daemon_chassisd.module_updater, 'num_modules', 1), \ + patch.object(daemon_chassisd.module_updater, 'get_module_admin_status', return_value='down'): + # Now run the function that sets the initial admin state + daemon_chassisd.set_initial_dpu_admin_state() + + +def test_set_initial_dpu_admin_state_up(): + # Test the chassisd run + chassis = MockSmartSwitchChassis() + + # DPU0 details + index = 0 + name = "DPU0" + desc = "DPU Module 0" + slot = 0 + sup_slot = 0 + serial = "DPU0-0000" + module_type = ModuleBase.MODULE_TYPE_DPU + module = MockModule(index, name, desc, module_type, slot, serial) + module.set_midplane_ip() + + # Set initial state for DPU0 + status = ModuleBase.MODULE_STATUS_PRESENT + module.set_oper_status(status) + chassis.module_list.append(module) + + # Supervisor ModuleUpdater + module_updater = SmartSwitchModuleUpdater(SYSLOG_IDENTIFIER, chassis) + module_updater.module_db_update() + module_updater.modules_num_update() + + # ChassisdDaemon setup + daemon_chassisd = ChassisdDaemon(SYSLOG_IDENTIFIER, chassis) + daemon_chassisd.module_updater = module_updater + daemon_chassisd.stop = MagicMock() + daemon_chassisd.stop.wait.return_value = True + daemon_chassisd.smartswitch = True + + # Import platform and use chassis as platform_chassis + import sonic_platform.platform + platform_chassis = chassis + + # Mock objects + mock_chassis = MagicMock() + mock_module_updater = MagicMock() + + # Mock the module (DPU0) + mock_module = MagicMock() + mock_module.get_name.return_value = "DPU0" + + # Mock chassis.get_module to return the mock_module for DPU0 + def mock_get_module(index): + if index == 0: # For DPU0 + return mock_module + return None # No other modules available in this test case + + # Apply the side effect for chassis.get_module + mock_chassis.get_module.side_effect = mock_get_module + + # Mock state_db + mock_state_db = MagicMock() + # fvs_mock = [True, {CHASSIS_MIDPLANE_INFO_ACCESS_FIELD: 'True'}] + # mock_state_db.get.return_value = fvs_mock + + # Mock db_connect + mock_db_connect = MagicMock() + mock_db_connect.return_value = mock_state_db + + # Mock admin_status + # mock_module_updater.get_module_admin_status.return_value = 'up' + + # Set access of DPU0 up + midplane_table = module_updater.midplane_table + module.set_midplane_reachable(False) + module_updater.check_midplane_reachability() + fvs = midplane_table.get(name) + assert fvs != None + if isinstance(fvs, list): + fvs = dict(fvs[-1]) + assert module.get_midplane_ip() == fvs[CHASSIS_MIDPLANE_INFO_IP_FIELD] + assert str(module.is_midplane_reachable()) == fvs[CHASSIS_MIDPLANE_INFO_ACCESS_FIELD] + + # Patching platform's Chassis object to return the mocked module + with patch.object(sonic_platform.platform.Chassis, 'is_smartswitch') as mock_is_smartswitch, \ + patch.object(sonic_platform.platform.Chassis, 'get_module', side_effect=mock_get_module): + + # Simulate that the system is a SmartSwitch + mock_is_smartswitch.return_value = True + + # Patch num_modules for the updater + with patch.object(daemon_chassisd.module_updater, 'num_modules', 1), \ + patch.object(daemon_chassisd.module_updater, 'get_module_admin_status', return_value='up'): + # Now run the function that sets the initial admin state + daemon_chassisd.set_initial_dpu_admin_state() + def test_daemon_run_supervisor_invalid_slot(): chassis = MockChassis()