From a5c68393bcff150eed9e48fd6ab07d611a7152fb Mon Sep 17 00:00:00 2001 From: GauravNagesh Date: Thu, 6 Nov 2025 11:06:00 +0000 Subject: [PATCH 1/2] thermalctld: Redis performance improvement --- sonic-thermalctld/scripts/thermalctld | 282 ++++++++++++++++++-------- 1 file changed, 195 insertions(+), 87 deletions(-) diff --git a/sonic-thermalctld/scripts/thermalctld b/sonic-thermalctld/scripts/thermalctld index 98f45702f..63484807b 100644 --- a/sonic-thermalctld/scripts/thermalctld +++ b/sonic-thermalctld/scripts/thermalctld @@ -69,6 +69,53 @@ class FanType(Enum): PSU = auto() MODULE = auto() +class FanDrawerStatus(): + + def __init__(self, fan_drawer=None): + """ + Initializer of FanDrawerStatus + """ + self.fan_drawer = fan_drawer + self.presence = True + self.status = True + self.led_status = NOT_AVAILABLE + + def set_presence(self, presence): + """ + Set and cache Fan drawer presence status + :param presence: Fan drawer presence status + :return: True if status changed else False + """ + if presence == self.presence: + return False + + self.presence = presence + return True + + def set_status(self, status): + """ + Set and cache Fan drawer status + :param status: Fan drawer status, False indicate Fault + :return: True if status changed else False + """ + if status == self.status: + return False + + self.status = status + return True + + def set_led_status(self, led_status): + """ + Set and cache Fan Drawer LED status + :param led_status: Fan Drawer LED status + :return: True if status changed else False + """ + if led_status == self.led_status: + return False + + self.led_status = led_status + return True + class FanStatus(logger.Logger): absent_fan_count = 0 faulty_fan_count = 0 @@ -87,6 +134,7 @@ class FanStatus(logger.Logger): self.over_speed = False self.invalid_direction = False self.led_initialized = False + self.led_status = NOT_AVAILABLE @classmethod def get_bad_fan_count(cls): @@ -157,6 +205,18 @@ class FanStatus(logger.Logger): self.over_speed = is_over_speed return old_status != self.over_speed + def set_led_status(self, led_status): + """ + Set and cache Fan LED status + :param led_status: Fan LED status + :return: True if status changed else False + """ + if led_status == self.led_status: + return False + + self.led_status = led_status + return True + def is_ok(self): """ Indicate the Fan works as expect @@ -177,7 +237,7 @@ class FanUpdater(logger.Logger): FAN_INFO_TABLE_NAME = 'FAN_INFO' FAN_DRAWER_INFO_TABLE_NAME = 'FAN_DRAWER_INFO' - def __init__(self, chassis, task_stopping_event): + def __init__(self, chassis, task_stopping_event, statedb_redisPipeline): """ Initializer for FanUpdater :param chassis: Object representing a platform chassis @@ -187,24 +247,28 @@ class FanUpdater(logger.Logger): self.chassis = chassis self.task_stopping_event = task_stopping_event self.fan_status_dict = {} - state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, FanUpdater.FAN_INFO_TABLE_NAME) - self.drawer_table = swsscommon.Table(state_db, FanUpdater.FAN_DRAWER_INFO_TABLE_NAME) - self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) + self.fan_drawer_status_dict = {} + self.table = swsscommon.Table(statedb_redisPipeline, FanUpdater.FAN_INFO_TABLE_NAME, True) + self.drawer_table = swsscommon.Table(statedb_redisPipeline, FanUpdater.FAN_DRAWER_INFO_TABLE_NAME, True) + self.phy_entity_table = swsscommon.Table(statedb_redisPipeline, PHYSICAL_ENTITY_INFO_TABLE, True) + self.first_run = True def __del__(self): - if self.table: - table_keys = self.table.getKeys() - for tk in table_keys: - self.table._del(tk) - if self.drawer_table: - drawer_keys = self.drawer_table.getKeys() - for dtk in drawer_keys: - self.drawer_table._del(dtk) - if self.phy_entity_table: - phy_entity_keys = self.phy_entity_table.getKeys() - for pek in phy_entity_keys: - self.phy_entity_table._del(pek) + try: + if self.table: + table_keys = self.table.getKeys() + for tk in table_keys: + self.table._del(tk) + if self.drawer_table: + drawer_keys = self.drawer_table.getKeys() + for dtk in drawer_keys: + self.drawer_table._del(dtk) + if self.phy_entity_table: + phy_entity_keys = self.phy_entity_table.getKeys() + for pek in phy_entity_keys: + self.phy_entity_table._del(pek) + except Exception as e: + self.log_error(f"Failed to delete FanUpdater tables from Redis : {type(e).__name__} : {e}") def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): """ @@ -269,23 +333,44 @@ class FanUpdater(logger.Logger): self.log_notice("Insufficient number of working fans warning cleared: all fans are back to normal") self.log_debug("End fan updating") + self.first_run = False def _refresh_fan_drawer_status(self, fan_drawer, drawer_index): drawer_name = try_get(fan_drawer.get_name) if drawer_name == NOT_AVAILABLE: return - update_entity_info(self.phy_entity_table, CHASSIS_INFO_KEY, drawer_name, fan_drawer, drawer_index) + try: + update_entity_info(self.phy_entity_table, CHASSIS_INFO_KEY, drawer_name, fan_drawer, drawer_index) + except Exception as e: + self.log_error(f"Failed to update fan drawer entity info for {drawer_name} to Redis : {type(e).__name__} : {e}") + + if drawer_name not in self.fan_drawer_status_dict: + self.fan_drawer_status_dict[drawer_name] = FanDrawerStatus(fan_drawer) - fvs = swsscommon.FieldValuePairs( - [('presence', str(try_get(fan_drawer.get_presence, False))), - ('model', str(try_get(fan_drawer.get_model))), - ('serial', str(try_get(fan_drawer.get_serial))), - ('status', str(try_get(fan_drawer.get_status))), - ('is_replaceable', str(try_get(fan_drawer.is_replaceable, False))), - ]) + drawer_status = self.fan_drawer_status_dict[drawer_name] - self.drawer_table.set(drawer_name, fvs) + presence = try_get(fan_drawer.get_presence, False) + status = try_get(fan_drawer.get_status, False) + + presence_changed = drawer_status.set_presence(presence) + status_changed = drawer_status.set_status(status) + + # Only update rarely-changing fields if changed + fvs = swsscommon.FieldValuePairs() + if presence_changed or self.first_run: + fvs.append(('presence', str(presence))) + fvs.append(('model', str(try_get(fan_drawer.get_model)))) + fvs.append(('serial', str(try_get(fan_drawer.get_serial)))) + fvs.append(('is_replaceable', str(try_get(fan_drawer.is_replaceable, False)))) + if status_changed or self.first_run: + fvs.append(('status', str(status))) + + if len(fvs) > 0: + try: + self.drawer_table.set(drawer_name, fvs) + except Exception as e: + self.log_error(f"Failed to write fan drawer status for {drawer_name} to Redis : {type(e).__name__} : {e}") def _refresh_fan_status(self, parent, parent_index, fan, fan_index, fan_type=FanType.DRAWER): """ @@ -328,7 +413,11 @@ class FanUpdater(logger.Logger): fan_direction = try_get(fan.get_direction) set_led = not fan_status.led_initialized - if fan_status.set_presence(presence): + presence_changed = fan_status.set_presence(presence) + fault_changed = False + under_speed_changed = fan_status.set_under_speed(is_under_speed) + over_speed_changed = fan_status.set_over_speed(is_over_speed) + if presence_changed: set_led = True self._log_on_status_changed(fan_status.presence, 'Fan removed warning cleared: {} was inserted'.format(fan_name), @@ -336,14 +425,14 @@ class FanUpdater(logger.Logger): 'the system, potential overheat hazard'.format(fan_name) ) - if presence and fan_status.set_fault_status(fan_fault_status): + if presence and (fault_changed := fan_status.set_fault_status(fan_fault_status)): set_led = True self._log_on_status_changed(fan_status.status, 'Fan fault warning cleared: {} is back to normal'.format(fan_name), 'Fan fault warning: {} is broken'.format(fan_name) ) - if presence and fan_status.set_under_speed(is_under_speed): + if presence and under_speed_changed: set_led = True self._log_on_status_changed(not fan_status.under_speed, 'Fan low speed warning cleared: {} speed is back to normal'.format(fan_name), @@ -351,7 +440,7 @@ class FanUpdater(logger.Logger): format(fan_name, speed, speed_target) ) - if presence and fan_status.set_over_speed(is_over_speed): + if presence and over_speed_changed: set_led = True self._log_on_status_changed(not fan_status.over_speed, 'Fan high speed warning cleared: {} speed is back to normal'.format(fan_name), @@ -367,20 +456,26 @@ class FanUpdater(logger.Logger): if fan_fault_status != NOT_AVAILABLE: fan_fault_status = fan_status.is_ok() - fvs = swsscommon.FieldValuePairs( - [('presence', str(presence)), - ('drawer_name', drawer_name), - ('model', str(try_get(fan.get_model))), - ('serial', str(try_get(fan.get_serial))), - ('status', str(fan_fault_status)), - ('direction', str(fan_direction)), - ('speed', str(speed)), - ('speed_target', str(speed_target)), - ('is_under_speed', str(is_under_speed)), - ('is_over_speed', str(is_over_speed)), - ('is_replaceable', str(is_replaceable)), - ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) - ]) + # Only update rarely-changing fields if changed + fvs = swsscommon.FieldValuePairs() + if presence_changed or self.first_run: + fvs.append(('presence', str(presence))) + fvs.append(('drawer_name', drawer_name)) + fvs.append(('model', str(try_get(fan.get_model)))) + fvs.append(('serial', str(try_get(fan.get_serial)))) + fvs.append(('speed_target', str(speed_target))) + fvs.append(('is_replaceable', str(is_replaceable))) + if fault_changed or self.first_run: + fvs.append(('status', str(fan_fault_status))) + if under_speed_changed or self.first_run: + fvs.append(('is_under_speed', str(is_under_speed))) + if over_speed_changed or self.first_run: + fvs.append(('is_over_speed', str(is_over_speed))) + + # Always update frequently-changing fields + fvs.append(('direction', str(fan_direction))) + fvs.append(('speed', str(speed))) + fvs.append(('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S'))) self.table.set(fan_name, fvs) @@ -409,33 +504,32 @@ class FanUpdater(logger.Logger): for fan_name, fan_status in self.fan_status_dict.items(): if self.task_stopping_event.is_set(): return - try: - fvs = swsscommon.FieldValuePairs([ - ('led_status', str(try_get(fan_status.fan.get_status_led))) - ]) - except Exception as e: - self.log_warning('Failed to get status LED state for fan {} - {}'.format(fan_name, e)) + + led_status = try_get(fan_status.fan.get_status_led, NOT_AVAILABLE) + led_status_changed = fan_status.set_led_status(led_status) + if led_status_changed or self.first_run: fvs = swsscommon.FieldValuePairs([ - ('led_status', NOT_AVAILABLE) + ('led_status', str(led_status)) ]) - self.table.set(fan_name, fvs) + try: + self.table.set(fan_name, fvs) + except Exception as e: + self.log_error(f"Failed to write fan LED status for {fan_name} to Redis : {type(e).__name__} : {e}") - for drawer in self.chassis.get_all_fan_drawers(): + for drawer_name, drawer_status in self.fan_drawer_status_dict.items(): if self.task_stopping_event.is_set(): return - drawer_name = try_get(drawer.get_name) - if drawer_name == NOT_AVAILABLE: - continue - try: - fvs = swsscommon.FieldValuePairs([ - ('led_status', str(try_get(drawer.get_status_led))) - ]) - except Exception as e: - self.log_warning('Failed to get status LED state for fan drawer') + + led_status = try_get(drawer_status.fan_drawer.get_status_led, NOT_AVAILABLE) + led_status_changed = drawer_status.set_led_status(led_status) + if led_status_changed or self.first_run: fvs = swsscommon.FieldValuePairs([ - ('led_status', NOT_AVAILABLE) + ('led_status', str(led_status)) ]) - self.drawer_table.set(drawer_name, fvs) + try: + self.drawer_table.set(drawer_name, fvs) + except Exception as e: + self.log_error(f"Failed to write fan drawer LED status for {drawer_name} to Redis : {type(e).__name__} : {e}") class TemperatureStatus(logger.Logger): @@ -525,7 +619,7 @@ class TemperatureUpdater(logger.Logger): # Temperature information table name in database TEMPER_INFO_TABLE_NAME = 'TEMPERATURE_INFO' - def __init__(self, chassis, task_stopping_event): + def __init__(self, chassis, task_stopping_event, statedb_redisPipeline): """ Initializer of TemperatureUpdater :param chassis: Object representing a platform chassis @@ -535,9 +629,8 @@ class TemperatureUpdater(logger.Logger): self.chassis = chassis self.task_stopping_event = task_stopping_event self.temperature_status_dict = {} - state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, TemperatureUpdater.TEMPER_INFO_TABLE_NAME) - self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) + self.table = swsscommon.Table(statedb_redisPipeline, TemperatureUpdater.TEMPER_INFO_TABLE_NAME, True) + self.phy_entity_table = swsscommon.Table(statedb_redisPipeline, PHYSICAL_ENTITY_INFO_TABLE, True) self.chassis_table = None self.all_thermals = set() @@ -552,27 +645,29 @@ class TemperatureUpdater(logger.Logger): # So catch the exception here and ignore it. table_name = TemperatureUpdater.TEMPER_INFO_TABLE_NAME+'_'+str(my_slot) chassis_state_db = daemon_base.db_connect("CHASSIS_STATE_DB") - self.chassis_table = swsscommon.Table(chassis_state_db, table_name) + self.chassis_statedb_redisPipeline = swsscommon.RedisPipeline(chassis_state_db, 10) + self.chassis_table = swsscommon.Table(self.chassis_statedb_redisPipeline, table_name, True) except Exception as e: self.chassis_table = None + self.log_warning(f"Failed to connect to CHASSIS_STATE_DB or create RedisPipeline : {type(e).__name__} : {e}") def __del__(self): - if self.table: - table_keys = self.table.getKeys() - for tk in table_keys: - self.table._del(tk) - try: + try: + if self.table: + table_keys = self.table.getKeys() + for tk in table_keys: + self.table._del(tk) if self.is_chassis_upd_required and self.chassis_table is not None: self.chassis_table._del(tk) - except Exception as e: # On a chassis system it is possible we may lose connection # to the supervisor and chassisdb. If this happens then we # should simply remove our handle to chassisdb. - self.chassis_table = None - if self.phy_entity_table: - phy_entity_keys = self.phy_entity_table.getKeys() - for pek in phy_entity_keys: - self.phy_entity_table._del(pek) + if self.phy_entity_table: + phy_entity_keys = self.phy_entity_table.getKeys() + for pek in phy_entity_keys: + self.phy_entity_table._del(pek) + except Exception as e: + self.log_error(f"Failed to delete TemperatureUpdater tables from Redis : {type(e).__name__} : {e}") def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): """ @@ -736,12 +831,14 @@ class TemperatureUpdater(logger.Logger): self.log_warning('Failed to update thermal status for {} - {}'.format(name, repr(e))) def _remove_thermal_from_db(self, thermal, parent_name, thermal_index): - name = try_get(thermal.get_name, '{} Thermal {}'.format(parent_name, thermal_index + 1)) - self.table._del(name) - - if self.chassis_table is not None: - self.chassis_table._del(name) + try: + name = try_get(thermal.get_name, '{} Thermal {}'.format(parent_name, thermal_index + 1)) + self.table._del(name) + if self.chassis_table is not None: + self.chassis_table._del(name) + except Exception as e: + self.log_error(f"Failed to remove thermal {name} from Redis : {type(e).__name__} : {e}") class ThermalMonitor(ThreadTaskBase): def __init__( @@ -768,13 +865,24 @@ class ThermalMonitor(ThreadTaskBase): # Set minimum logging level to INFO self.logger.set_min_log_priority_info() - self.fan_updater = FanUpdater(chassis, self.task_stopping_event) - self.temperature_updater = TemperatureUpdater(chassis, self.task_stopping_event) + try: + # Create a single STATE_DB connection and RedisPipeline to prevent redundant connection creation since they are sequential calls + state_db = daemon_base.db_connect("STATE_DB") + self.statedb_redisPipeline = swsscommon.RedisPipeline(state_db, 10) + except Exception as e: + self.logger.log_error(f"Failed to connect to STATE_DB or create RedisPipeline : {type(e).__name__} : {e}") + + self.fan_updater = FanUpdater(chassis, self.task_stopping_event, self.statedb_redisPipeline) + self.temperature_updater = TemperatureUpdater(chassis, self.task_stopping_event, self.statedb_redisPipeline) def main(self): begin = time.time() self.fan_updater.update() self.temperature_updater.update() + try: + self.statedb_redisPipeline.flush() + except Exception as e: + self.logger.log_error(f"Failed to flush RedisPipeline : {type(e).__name__} : {e}") elapsed = time.time() - begin if elapsed < self.update_interval: self.wait_time = self.update_interval - elapsed From 575be6945fd717a4693b4f1dc7387d1869afde66 Mon Sep 17 00:00:00 2001 From: GauravNagesh Date: Thu, 6 Nov 2025 11:07:07 +0000 Subject: [PATCH 2/2] thermalctld: new tests for conditional write to DB and changes to mocked_libs --- .../mocked_libs/swsscommon/swsscommon.py | 27 +- sonic-thermalctld/tests/test_thermalctld.py | 629 +++++++++++++++++- 2 files changed, 617 insertions(+), 39 deletions(-) diff --git a/sonic-thermalctld/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-thermalctld/tests/mocked_libs/swsscommon/swsscommon.py index 13c49dec1..1f6703004 100644 --- a/sonic-thermalctld/tests/mocked_libs/swsscommon/swsscommon.py +++ b/sonic-thermalctld/tests/mocked_libs/swsscommon/swsscommon.py @@ -6,9 +6,19 @@ STATE_DB = '' +class RedisPipeline: + def __init__(self, db, batch_size=128): + self.db = db + self.batch_size = batch_size + self.queue = [] + + def flush(self): + # Mock flush operation - just clear the queue + self.queue.clear() + pass class Table: - def __init__(self, db, table_name): + def __init__(self, db_or_redispipeline, table_name, buffered=False): self.table_name = table_name self.mock_dict = {} @@ -30,15 +40,24 @@ def get_size(self): class FieldValuePairs: - fv_dict = {} - def __init__(self, tuple_list): - if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): + def __init__(self, tuple_list=None): + if tuple_list is None: + self.fv_dict = {} + elif isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): self.fv_dict = dict(tuple_list) + else: + self.fv_dict = dict() + + def append(self, kv_tuple): + self.fv_dict[kv_tuple[0]] = kv_tuple[1] def __setitem__(self, key, kv_tuple): self.fv_dict[kv_tuple[0]] = kv_tuple[1] + def __len__(self): + return len(self.fv_dict) + def __getitem__(self, key): return self.fv_dict[key] diff --git a/sonic-thermalctld/tests/test_thermalctld.py b/sonic-thermalctld/tests/test_thermalctld.py index fbfe5ea09..ab73d9f7e 100644 --- a/sonic-thermalctld/tests/test_thermalctld.py +++ b/sonic-thermalctld/tests/test_thermalctld.py @@ -24,8 +24,8 @@ from sonic_py_common import daemon_base -from .mock_platform import MockChassis, MockFan, MockModule, MockPsu, MockSfp, MockThermal -from .mock_swsscommon import Table +from .mock_platform import MockChassis, MockFan, MockModule, MockPsu, MockSfp, MockThermal, MockFanDrawer +from swsscommon.swsscommon import Table, RedisPipeline daemon_base.db_connect = mock.MagicMock() @@ -123,6 +123,61 @@ def test_set_over_speed(self): assert not ret +class TestFanDrawerStatus(object): + """ + Test cases to cover functionality in FanDrawerStatus class + """ + def test_set_presence(self): + fan_drawer_status = thermalctld.FanDrawerStatus() + ret = fan_drawer_status.set_presence(True) + assert fan_drawer_status.presence + assert not ret + + ret = fan_drawer_status.set_presence(False) + assert not fan_drawer_status.presence + assert ret + + def test_set_status(self): + fan_drawer_status = thermalctld.FanDrawerStatus() + + ret = fan_drawer_status.set_status(True) + assert not ret + + ret = fan_drawer_status.set_status(False) + assert ret + assert not fan_drawer_status.status + + ret = fan_drawer_status.set_status(False) + assert not ret + + ret = fan_drawer_status.set_status(True) + assert ret + assert fan_drawer_status.status + + ret = fan_drawer_status.set_status(True) + assert not ret + + def test_set_led_status(self): + fan_drawer_status = thermalctld.FanDrawerStatus() + + ret = fan_drawer_status.set_led_status(thermalctld.NOT_AVAILABLE) + assert not ret + + ret = fan_drawer_status.set_led_status('green') + assert ret + assert fan_drawer_status.led_status == 'green' + + ret = fan_drawer_status.set_led_status('green') + assert not ret + + ret = fan_drawer_status.set_led_status('red') + assert ret + assert fan_drawer_status.led_status == 'red' + + ret = fan_drawer_status.set_led_status('red') + assert not ret + + class TestFanUpdater(object): """ Test cases to cover functionality in FanUpdater class @@ -131,7 +186,7 @@ class TestFanUpdater(object): @mock.patch('thermalctld.update_entity_info', mock.MagicMock()) def test_refresh_fan_drawer_status_fan_drawer_get_name_not_impl(self): # Test case where fan_drawer.get_name is not implemented - fan_updater = thermalctld.FanUpdater(MockChassis(), threading.Event()) + fan_updater = thermalctld.FanUpdater(MockChassis(), threading.Event(), RedisPipeline(mock.MagicMock())) mock_fan_drawer = mock.MagicMock() fan_updater._refresh_fan_drawer_status(mock_fan_drawer, 1) assert thermalctld.update_entity_info.call_count == 0 @@ -145,7 +200,7 @@ def test_update_fan_with_exception(self): fan.make_over_speed() chassis.get_all_fans().append(fan) - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) fan_updater.update() assert fan.get_status_led() == MockFan.STATUS_LED_COLOR_RED assert fan_updater.log_warning.call_count == 1 @@ -162,7 +217,7 @@ def test_set_fan_led_exception(self): mock_fan = MockFan() mock_fan.set_status_led = mock.MagicMock(side_effect=NotImplementedError) - fan_updater = thermalctld.FanUpdater(MockChassis(), threading.Event()) + fan_updater = thermalctld.FanUpdater(MockChassis(), threading.Event(), RedisPipeline(mock.MagicMock())) fan_updater._set_fan_led(mock_fan_drawer, mock_fan, 'Test Fan', fan_status) assert fan_updater.log_warning.call_count == 1 fan_updater.log_warning.assert_called_with('Failed to set status LED for fan Test Fan, set_status_led not implemented') @@ -170,8 +225,16 @@ def test_set_fan_led_exception(self): def test_fan_absent(self): chassis = MockChassis() chassis.make_absent_fan() - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table = mock.MagicMock() + fan_updater.drawer_table = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update - all fields should be written + assert fan_updater.first_run is True fan_updater.update() + assert fan_updater.first_run is False + fan_list = chassis.get_all_fans() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_RED assert fan_updater.log_warning.call_count == 2 @@ -181,6 +244,32 @@ def test_fan_absent(self): ] assert fan_updater.log_warning.mock_calls == expected_calls + # Verify all fields written on first run for absent fan + assert fan_updater.table.set.call_count == 1 + fan_name = 'FanDrawer 0 fan 1' + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + # Presence-related fields should be present on first run + assert 'presence' in fv_dict + assert fv_dict['presence'] == 'False' + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'status' in fv_dict + assert 'is_under_speed' in fv_dict + assert 'is_over_speed' in fv_dict + # Frequently-changing fields should always be present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + + # Second update - presence changes + fan_updater.table.set.reset_mock() fan_list[0].set_presence(True) fan_updater.update() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_GREEN @@ -191,10 +280,38 @@ def test_fan_absent(self): ] assert fan_updater.log_notice.mock_calls == expected_calls + # Verify presence-related fields are written when presence changes + assert fan_updater.table.set.call_count == 1 + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + # Presence changed, so these should be written + assert 'presence' in fv_dict + assert fv_dict['presence'] == 'True' + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'status' not in fv_dict + assert 'is_under_speed' not in fv_dict + assert 'is_over_speed' not in fv_dict + # Always present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + def test_fan_faulty(self): chassis = MockChassis() chassis.make_faulty_fan() - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table = mock.MagicMock() + fan_updater.drawer_table = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update fan_updater.update() fan_list = chassis.get_all_fans() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_RED @@ -205,6 +322,31 @@ def test_fan_faulty(self): ] assert fan_updater.log_warning.mock_calls == expected_calls + # Verify status field is written on first run + assert fan_updater.table.set.call_count == 1 + fan_name = 'FanDrawer 0 fan 1' + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'status' in fv_dict + assert fv_dict['status'] == 'False' + assert 'presence' in fv_dict + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'is_under_speed' in fv_dict + assert 'is_over_speed' in fv_dict + # Frequently-changing fields always present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + + # Second update - status changes + fan_updater.table.set.reset_mock() fan_list[0].set_status(True) fan_updater.update() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_GREEN @@ -215,45 +357,178 @@ def test_fan_faulty(self): ] assert fan_updater.log_notice.mock_calls == expected_calls + # Verify status field is written when it changes + assert fan_updater.table.set.call_count == 1 + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'status' in fv_dict + assert fv_dict['status'] == 'True' + # Frequently-changing fields always present + assert 'speed' in fv_dict + assert 'timestamp' in fv_dict + # Presence didn't change, so these should NOT be present + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'serial' not in fv_dict + assert 'is_under_speed' not in fv_dict + assert 'is_over_speed' not in fv_dict + def test_fan_under_speed(self): chassis = MockChassis() chassis.make_under_speed_fan() - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table = mock.MagicMock() + fan_updater.drawer_table = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update fan_updater.update() fan_list = chassis.get_all_fans() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_RED assert fan_updater.log_warning.call_count == 1 fan_updater.log_warning.assert_called_with('Fan low speed warning: FanDrawer 0 fan 1 current speed=1, target speed=2') + # Verify is_under_speed field is written on first run + assert fan_updater.table.set.call_count == 1 + fan_name = 'FanDrawer 0 fan 1' + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'is_under_speed' in fv_dict + assert fv_dict['is_under_speed'] == 'True' + # Other fields should also be present on first run + assert 'presence' in fv_dict + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'status' in fv_dict + assert 'is_over_speed' in fv_dict + # Frequently-changing fields always present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + + + # Second update - speed changes + fan_updater.table.set.reset_mock() fan_list[0].make_normal_speed() fan_updater.update() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_GREEN assert fan_updater.log_notice.call_count == 1 fan_updater.log_notice.assert_called_with('Fan low speed warning cleared: FanDrawer 0 fan 1 speed is back to normal') + # Verify is_under_speed field is written when it changes + assert fan_updater.table.set.call_count == 1 + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'is_under_speed' in fv_dict + assert fv_dict['is_under_speed'] == 'False' + # Frequently-changing fields always present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + # Presence didn't change + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'serial' not in fv_dict + assert 'status' not in fv_dict + assert 'is_over_speed' not in fv_dict + def test_fan_over_speed(self): chassis = MockChassis() chassis.make_over_speed_fan() - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + + # Mock the table.set to track calls + fan_updater.table.set = mock.MagicMock() + fan_updater.drawer_table.set = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update fan_updater.update() fan_list = chassis.get_all_fans() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_RED assert fan_updater.log_warning.call_count == 1 fan_updater.log_warning.assert_called_with('Fan high speed warning: FanDrawer 0 fan 1 current speed=2, target speed=1') + # Verify is_over_speed field is written on first run + assert fan_updater.table.set.call_count == 1 + fan_name = 'FanDrawer 0 fan 1' + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'is_over_speed' in fv_dict + assert fv_dict['is_over_speed'] == 'True' + # Other fields should also be present on first run + assert 'presence' in fv_dict + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'status' in fv_dict + assert 'is_under_speed' in fv_dict + # Frequently-changing fields always present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + + # Second update - speed changes + fan_updater.table.set.reset_mock() fan_list[0].make_normal_speed() fan_updater.update() assert fan_list[0].get_status_led() == MockFan.STATUS_LED_COLOR_GREEN assert fan_updater.log_notice.call_count == 1 fan_updater.log_notice.assert_called_with('Fan high speed warning cleared: FanDrawer 0 fan 1 speed is back to normal') + # Verify is_over_speed field is written when it changes + assert fan_updater.table.set.call_count == 1 + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'is_over_speed' in fv_dict + assert fv_dict['is_over_speed'] == 'False' + # Frequently-changing fields always present + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + # Presence didn't change + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'serial' not in fv_dict + assert 'status' not in fv_dict + assert 'is_under_speed' not in fv_dict + def test_update_psu_fans(self): chassis = MockChassis() psu = MockPsu() mock_fan = MockFan() psu._fan_list.append(mock_fan) chassis._psu_list.append(psu) - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) fan_updater.update() assert fan_updater.log_warning.call_count == 0 @@ -274,7 +549,7 @@ def test_update_module_fans(self): chassis.set_modular_chassis(True) module._fan_list.append(mock_fan) chassis._module_list.append(module) - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) fan_updater.update() assert fan_updater.log_warning.call_count == 0 @@ -288,6 +563,183 @@ def test_update_module_fans(self): else: fan_updater.log_warning.assert_called_with("Failed to update module fan status - Exception('Test message',)") + def test_fan_conditional_write_no_change(self): + """Test that only frequently-changing fields are written when nothing changes""" + chassis = MockChassis() + mock_fan = MockFan() + fan_drawer = MockFanDrawer(len(chassis._fan_drawer_list)) + fan_drawer._fan_list.append(mock_fan) + chassis._fan_list.append(mock_fan) + chassis._fan_drawer_list.append(fan_drawer) + + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table = mock.MagicMock() + fan_updater.drawer_table = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update + fan_updater.update() + + # Reset mock to track subsequent calls + fan_updater.table.set.reset_mock() + + # Second update - no changes to fan state + fan_updater.update() + + # Should still write (for frequently-changing fields) + assert fan_updater.table.set.call_count == 1 + + # Verify that only frequently-changing fields are written + fan_name = 'FanDrawer 0 fan 1' + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + + # These fields should always be present (frequently-changing) + assert 'speed' in fv_dict + assert 'direction' in fv_dict + assert 'timestamp' in fv_dict + + # These fields should NOT be present (rarely-changing, no change) + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'serial' not in fv_dict + assert 'status' not in fv_dict + assert 'is_under_speed' not in fv_dict + assert 'is_over_speed' not in fv_dict + + def test_fan_drawer_conditional_write(self): + """Test fan drawer conditional write on first run and when status changes""" + chassis = MockChassis() + mock_fan = MockFan() + fan_drawer = MockFanDrawer(len(chassis._fan_drawer_list)) + fan_drawer._fan_list.append(mock_fan) + chassis._fan_list.append(mock_fan) + chassis._fan_drawer_list.append(fan_drawer) + + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table = mock.MagicMock() + fan_updater.drawer_table = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update - all drawer fields should be written + fan_updater.update() + + # Check that drawer_table.set was called + assert fan_updater.drawer_table.set.call_count == 1 + + # Get the first call and verify all fields present + drawer_name = 'FanDrawer 0' + matching_call = None + for call in fan_updater.drawer_table.set.call_args_list: + if call[0][0] == drawer_name: + matching_call = call + break + + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'presence' in fv_dict + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'is_replaceable' in fv_dict + assert 'status' in fv_dict + + + # Second update - no changes, should not write anything + fan_updater.drawer_table.set.reset_mock() + fan_updater.update() + assert fan_updater.drawer_table.set.call_count == 0 + + + # Third update - status changes + drawer_list = chassis.get_all_fan_drawers() + drawer_list[0].set_status(False) + fan_updater.update() + + # Should write status field + assert fan_updater.drawer_table.set.call_count == 1 + + matching_call = None + for call in fan_updater.drawer_table.set.call_args_list: + if call[0][0] == drawer_name: + matching_call = call + break + + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'status' in fv_dict + # Other fields should NOT be written (not changed) + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'serial' not in fv_dict + + def test_update_led_color_conditional_write(self): + """Test LED color conditional write""" + chassis = MockChassis() + mock_fan = MockFan() + fan_drawer = MockFanDrawer(len(chassis._fan_drawer_list)) + fan_drawer._fan_list.append(mock_fan) + chassis._fan_list.append(mock_fan) + chassis._fan_drawer_list.append(fan_drawer) + + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table.set = mock.MagicMock() + fan_updater.drawer_table.set = mock.MagicMock() + + # First update and LED update + fan_updater.update() + fan_updater.table.set.reset_mock() + fan_updater.drawer_table.set.reset_mock() + fan_updater.first_run = True + fan_updater._update_led_color() + + # Should write LED status on first run + assert fan_updater.table.set.call_count == 1 + fan_name = 'FanDrawer 0 fan 1' + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + assert 'led_status' in fvs.fv_dict + + # Second LED update - no change + fan_updater.table.set.reset_mock() + fan_updater.drawer_table.set.reset_mock() + fan_updater.first_run = False + fan_updater._update_led_color() + + # Should NOT write (no change) + assert fan_updater.table.set.call_count == 0 + assert fan_updater.drawer_table.set.call_count == 0 + + # Third update - LED changes + fan_list = chassis.get_all_fans() + fan_list[0].set_status_led(MockFan.STATUS_LED_COLOR_RED) + fan_updater._update_led_color() + + # Should write LED status (changed) + assert fan_updater.table.set.call_count == 1 + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + assert 'led_status' in fvs.fv_dict + assert fvs.fv_dict['led_status'] == MockFan.STATUS_LED_COLOR_RED + class TestThermalMonitor(object): """ Test cases to cover functionality in ThermalMonitor class @@ -321,7 +773,12 @@ def test_insufficient_fan_number(): chassis = MockChassis() chassis.make_absent_fan() chassis.make_faulty_fan() - fan_updater = thermalctld.FanUpdater(chassis, threading.Event()) + fan_updater = thermalctld.FanUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) + fan_updater.table = mock.MagicMock() + fan_updater.drawer_table = mock.MagicMock() + fan_updater._update_led_color = mock.MagicMock() + + # First update fan_updater.update() assert fan_updater.log_warning.call_count == 3 expected_calls = [ @@ -331,12 +788,80 @@ def test_insufficient_fan_number(): ] assert fan_updater.log_warning.mock_calls == expected_calls + # Verify both fans' data written on first run + fan0_name = 'FanDrawer 0 fan 1' + fan1_name = 'FanDrawer 1 fan 1' + + # Check fan 0 (absent) + assert fan_updater.table.set.call_count == 2 + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan0_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'presence' in fv_dict + assert fv_dict['presence'] == 'False' + + # Check fan 1 (faulty) + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan1_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'status' in fv_dict + assert fv_dict['status'] == 'False' + + # Second update - fan 0 presence changes + fan_updater.table.set.reset_mock() fan_list = chassis.get_all_fans() fan_list[0].set_presence(True) fan_updater.update() assert fan_updater.log_notice.call_count == 1 fan_updater.log_warning.assert_called_with('Insufficient number of working fans warning: 1 fan is not working') - + assert fan_updater.table.set.call_count == 2 + + # Verify fan 0 has presence fields written (changed) + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan0_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'presence' in fv_dict + assert fv_dict['presence'] == 'True' + assert 'model' in fv_dict + assert 'serial' in fv_dict + assert 'status' not in fv_dict # status didn't change + assert 'is_under_speed' not in fv_dict # is_under_speed didn't change + assert 'is_over_speed' not in fv_dict # is_over_speed didn't change + + # Verify fan 1 has only frequently-changing fields (no change) + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan1_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'speed' in fv_dict + assert 'timestamp' in fv_dict + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'status' not in fv_dict # status didn't change + assert 'is_under_speed' not in fv_dict # is_under_speed didn't change + assert 'is_over_speed' not in fv_dict # is_over_speed didn't change + + # Third update - fan 1 status changes + fan_updater.table.set.reset_mock() fan_list[1].set_status(True) fan_updater.update() assert fan_updater.log_notice.call_count == 3 @@ -346,6 +871,41 @@ def test_insufficient_fan_number(): mock.call('Insufficient number of working fans warning cleared: all fans are back to normal') ] assert fan_updater.log_notice.mock_calls == expected_calls + assert fan_updater.table.set.call_count == 2 + + # Verify fan 0 has only frequently-changing fields (no change) + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan0_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'speed' in fv_dict + assert 'timestamp' in fv_dict + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'status' not in fv_dict + assert 'is_under_speed' not in fv_dict + assert 'is_over_speed' not in fv_dict + + # Verify fan 1 has status field written (changed) + matching_call = None + for call in fan_updater.table.set.call_args_list: + if call[0][0] == fan1_name: + matching_call = call + break + assert matching_call is not None + fvs = matching_call[0][1] + fv_dict = fvs.fv_dict + assert 'status' in fv_dict + # Presence didn't change + assert 'presence' not in fv_dict + assert 'model' not in fv_dict + assert 'serial' not in fv_dict + assert 'is_under_speed' not in fv_dict + assert 'is_over_speed' not in fv_dict def test_temperature_status_set_over_temper(): @@ -405,7 +965,7 @@ class TestTemperatureUpdater(object): """ def test_deinit(self): chassis = MockChassis() - temp_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temp_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temp_updater.temperature_status_dict = {'key1': 'value1', 'key2': 'value2'} temp_updater.table = Table("STATE_DB", "xtable") temp_updater.table._del = mock.MagicMock() @@ -427,7 +987,7 @@ def test_deinit(self): def test_deinit_exception(self): chassis = MockChassis() - temp_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temp_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temp_updater.temperature_status_dict = {'key1': 'value1', 'key2': 'value2'} temp_updater.table = Table("STATE_DB", "xtable") temp_updater.table._del = mock.MagicMock() @@ -443,15 +1003,14 @@ def test_deinit_exception(self): temp_updater.__del__() assert temp_updater.table.getKeys.call_count == 1 - assert temp_updater.table._del.call_count == 2 - expected_calls = [mock.call('key1'), mock.call('key2')] + assert temp_updater.table._del.call_count == 1 + expected_calls = [mock.call('key1')] temp_updater.table._del.assert_has_calls(expected_calls, any_order=True) - assert temp_updater.chassis_table is None def test_over_temper(self): chassis = MockChassis() chassis.make_over_temper_thermal() - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() thermal_list = chassis.get_all_thermals() assert temperature_updater.log_warning.call_count == 1 @@ -465,7 +1024,7 @@ def test_over_temper(self): def test_under_temper(self): chassis = MockChassis() chassis.make_under_temper_thermal() - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() thermal_list = chassis.get_all_thermals() assert temperature_updater.log_warning.call_count == 1 @@ -482,7 +1041,7 @@ def test_update_psu_thermals(self): mock_thermal = MockThermal() psu._thermal_list.append(mock_thermal) chassis._psu_list.append(psu) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() assert temperature_updater.log_warning.call_count == 0 @@ -502,7 +1061,7 @@ def test_update_sfp_thermals(self): mock_thermal = MockThermal() sfp._thermal_list.append(mock_thermal) chassis._sfp_list.append(sfp) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() assert temperature_updater.log_warning.call_count == 0 @@ -523,7 +1082,7 @@ def test_update_thermal_with_exception(self): thermal.make_over_temper() chassis.get_all_thermals().append(thermal) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() assert temperature_updater.log_warning.call_count == 2 @@ -544,7 +1103,7 @@ def test_update_module_thermals(self): chassis = MockChassis() chassis.make_module_thermal() chassis.set_modular_chassis(True) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() assert len(temperature_updater.all_thermals) == 3 @@ -559,21 +1118,21 @@ def test_dpu_chassis_thermals(): # Modular chassis (Not a dpu chassis) No Change in TemperatureUpdater Behaviour chassis.set_modular_chassis(True) chassis.set_my_slot(1) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert temperature_updater.chassis_table # DPU chassis TemperatureUpdater without is_smartswitch False return - No update to CHASSIS_STATE_DB chassis.set_modular_chassis(False) chassis.set_dpu(True) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert not temperature_updater.chassis_table # DPU chassis TemperatureUpdater without get_dpu_id implmenetation- No update to CHASSIS_STATE_DB chassis.set_smartswitch(True) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert not temperature_updater.chassis_table # DPU chassis TemperatureUpdater with get_dpu_id implemented - Update data to CHASSIS_STATE_DB dpu_id = 1 chassis.set_dpu_id(dpu_id) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert temperature_updater.chassis_table # Table name in chassis state db = TEMPERATURE_INFO_0 for dpu_id 0 assert temperature_updater.chassis_table.table_name == f"{TEMPER_INFO_TABLE_NAME}_{dpu_id}" @@ -588,7 +1147,7 @@ def test_dpu_chassis_state_deinit(): chassis.set_modular_chassis(False) chassis.set_dpu(True) chassis.set_dpu_id(1) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert temperature_updater.chassis_table temperature_updater.table = Table("STATE_DB", "xtable") temperature_updater.phy_entity_table = None @@ -611,7 +1170,7 @@ def test_updater_dpu_thermal_check_chassis_table(): chassis.set_dpu(True) chassis.set_smartswitch(True) chassis.set_dpu_id(1) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() assert temperature_updater.chassis_table.get_size() == chassis.get_num_thermals() @@ -628,17 +1187,17 @@ def test_updater_thermal_check_modular_chassis(): chassis = MockChassis() assert chassis.is_modular_chassis() == False - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert temperature_updater.chassis_table == None chassis.set_modular_chassis(True) chassis.set_my_slot(-1) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert temperature_updater.chassis_table == None my_slot = 1 chassis.set_my_slot(my_slot) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) assert temperature_updater.chassis_table != None assert temperature_updater.chassis_table.table_name == '{}_{}'.format(TEMPER_INFO_TABLE_NAME, str(my_slot)) @@ -651,7 +1210,7 @@ def test_updater_thermal_check_chassis_table(): chassis.set_modular_chassis(True) chassis.set_my_slot(1) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() assert temperature_updater.chassis_table.get_size() == chassis.get_num_thermals() @@ -670,7 +1229,7 @@ def test_updater_thermal_check_min_max(): chassis.set_modular_chassis(True) chassis.set_my_slot(1) - temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event()) + temperature_updater = thermalctld.TemperatureUpdater(chassis, threading.Event(), RedisPipeline(mock.MagicMock())) temperature_updater.update() slot_dict = temperature_updater.chassis_table.get(thermal.get_name())