From 7b363f3d028c03d25477a4088d46ef8e1f017a8c Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Mon, 19 Jun 2023 11:10:57 -0700 Subject: [PATCH 01/10] Thermalctld changes for Voltage sensor monitor. --- sonic-thermalctld/scripts/thermalctld | 237 ++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/sonic-thermalctld/scripts/thermalctld b/sonic-thermalctld/scripts/thermalctld index 806bf5413..6690e1f45 100644 --- a/sonic-thermalctld/scripts/thermalctld +++ b/sonic-thermalctld/scripts/thermalctld @@ -731,6 +731,241 @@ class TemperatureUpdater(logger.Logger): self.chassis_table._del(name) +class VoltageStatus(logger.Logger): + + def __init__(self): + super(VoltageStatus, self).__init__(SYSLOG_IDENTIFIER) + + self.voltage = None + self.over_voltage = False + self.under_voltage = False + + def set_voltage(self, name, voltage): + """ + Record voltage changes. + :param name: Name of the voltage sensor. + :param voltage: New voltage value. + :return: + """ + if voltage == NOT_AVAILABLE: + if self.voltage is not None: + self.log_warning('Voltage of {} became unavailable'.format(name)) + self.voltage = None + return + + self.voltage = voltage + + def set_over_voltage(self, voltage, threshold): + """ + Set over voltage status + :param voltage: voltage + :param threshold: High threshold + :return: True if over voltage status changed else False + """ + if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + self.log_warning('Voltage/threshold of {} became unavailable {}/{}'.format(self.name, voltage, threshold)) + old_status = self.over_voltage + self.over_voltage = False + return old_status != self.over_voltage + + status = voltage > threshold + if status == self.over_voltage: + return False + + self.over_voltage = status + return True + + def set_under_voltage(self, voltage, threshold): + """ + Set under voltage status + :param voltage: voltage + :param threshold: Low threshold + :return: True if under voltage status changed else False + """ + if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + old_status = self.under_voltage + self.under_voltage = False + return old_status != self.under_voltage + + status = voltage < threshold + if status == self.under_voltage: + return False + + self.under_voltage = status + return True + + +# +# VoltageUpdater ====================================================================== +# +class VoltageUpdater(logger.Logger): + # Voltage information table name in database + VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' + + def __init__(self, chassis, task_stopping_event): + """ + Initializer of VoltageUpdater + :param chassis: Object representing a platform chassis + """ + super(VoltageUpdater, self).__init__(SYSLOG_IDENTIFIER) + + self.chassis = chassis + self.task_stopping_event = task_stopping_event + self.voltage_status_dict = {} + state_db = daemon_base.db_connect("STATE_DB") + self.table = swsscommon.Table(state_db, VoltageUpdater.VOLTAGE_INFO_TABLE_NAME) + self.chassis_table = None + + self.is_chassis_system = chassis.is_modular_chassis() + if self.is_chassis_system: + self.module_vsensors = set() + my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) + if my_slot != INVALID_SLOT: + try: + # Modular chassis does not have to have table CHASSIS_STATE_DB. + # So catch the exception here and ignore it. + table_name = VoltageUpdater.VOLTAGE_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) + except Exception as e: + self.chassis_table = None + + def __del__(self): + if self.table: + table_keys = self.table.getKeys() + for tk in table_keys: + self.table._del(tk) + if self.is_chassis_system and self.chassis_table is not None: + self.chassis_table._del(tk) + + def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): + """ + Log when any status changed + :param normal_status: Expected status. + :param normal_log: Log string for expected status. + :param abnormal_log: Log string for unexpected status + :return: + """ + if normal_status: + self.log_notice(normal_log) + else: + self.log_warning(abnormal_log) + + def update(self): + """ + Update all voltage information to database + :return: + """ + self.log_debug("Start voltage updating") + for index, voltage_sensor in enumerate(self.chassis.get_all_vsensors()): + if self.task_stopping_event.is_set(): + return + + print(index, voltage_sensor) + self._refresh_voltage_status(CHASSIS_INFO_KEY, voltage_sensor, index) + + if self.is_chassis_system: + available_vsensors = set() + for module_index, module in enumerate(self.chassis.get_all_modules()): + module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) + + for vsensor_index, vsensor in enumerate(module.get_all_vsensors()): + if self.task_stopping_event.is_set(): + return + + available_vsensors.add((vsensor, module_name, vsensor_index)) + self._refresh_voltage_status(module_name, vsensor, vsensor_index) + + vsensors_to_remove = self.module_vsensors - available_vsensors + self.module_vsensors = available_vsensors + for vsensor, parent_name, vsensor_index in vsensors_to_remove: + self._remove_vsensor_from_db(vsensor, parent_name, vsensor_index) + + self.log_debug("End Voltage updating") + + def _refresh_voltage_status(self, parent_name, vsensor, vsensor_index): + """ + Get voltage status by platform API and write to database + :param parent_name: Name of parent device of the vsensor object + :param vsensor: Object representing a platform voltage vsensor + :param vsensor_index: Index of the vsensor object in platform chassis + :return: + """ + try: + name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) + print(name) + + if name not in self.voltage_status_dict: + self.voltage_status_dict[name] = VoltageStatus() + + print(self.voltage_status_dict) + + voltage_status = self.voltage_status_dict[name] + print(voltage_status) + + high_threshold = NOT_AVAILABLE + low_threshold = NOT_AVAILABLE + high_critical_threshold = NOT_AVAILABLE + low_critical_threshold = NOT_AVAILABLE + maximum_voltage = NOT_AVAILABLE + minimum_voltage = NOT_AVAILABLE + voltage = try_get(vsensor.get_voltage) + print(voltage) + is_replaceable = try_get(vsensor.is_replaceable, False) + if voltage != NOT_AVAILABLE: + voltage_status.set_voltage(name, voltage) + minimum_voltage = try_get(vsensor.get_minimum_recorded) + maximum_voltage = try_get(vsensor.get_maximum_recorded) + high_threshold = try_get(vsensor.get_high_threshold) + low_threshold = try_get(vsensor.get_low_threshold) + high_critical_threshold = try_get(vsensor.get_high_critical_threshold) + low_critical_threshold = try_get(vsensor.get_low_critical_threshold) + + warning = False + if voltage != NOT_AVAILABLE and voltage_status.set_over_voltage(voltage, high_threshold): + self._log_on_status_changed(not voltage_status.over_voltage, + 'High voltage warning cleared: {} voltage restored to {}C, high threshold {}C'. + format(name, voltage, high_threshold), + 'High voltage warning: {} current voltage {}C, high threshold {}C'. + format(name, voltage, high_threshold) + ) + warning = warning | voltage_status.over_voltage + + if voltage != NOT_AVAILABLE and voltage_status.set_under_voltage(voltage, low_threshold): + self._log_on_status_changed(not voltage_status.under_voltage, + 'Low voltage warning cleared: {} voltage restored to {}C, low threshold {}C'. + format(name, voltage, low_threshold), + 'Low voltage warning: {} current voltage {}C, low threshold {}C'. + format(name, voltage, low_threshold) + ) + warning = warning | voltage_status.under_voltage + + fvs = swsscommon.FieldValuePairs( + [('voltage', str(voltage)), + ('minimum_voltage', str(minimum_voltage)), + ('maximum_voltage', str(maximum_voltage)), + ('high_threshold', str(high_threshold)), + ('low_threshold', str(low_threshold)), + ('warning_status', str(warning)), + ('critical_high_threshold', str(high_critical_threshold)), + ('critical_low_threshold', str(low_critical_threshold)), + ('is_replaceable', str(is_replaceable)), + ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) + ]) + + self.table.set(name, fvs) + if self.is_chassis_system and self.chassis_table is not None: + self.chassis_table.set(name, fvs) + except Exception as e: + self.log_warning('Failed to update vsensor status for {} - {}'.format(name, repr(e))) + + def _remove_vsensor_from_db(self, vsensor, parent_name, vsensor_index): + name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) + self.table._del(name) + + if self.chassis_table is not None: + self.chassis_table._del(name) + class ThermalMonitor(ProcessTaskBase): # Initial update interval INITIAL_INTERVAL = 5 @@ -758,11 +993,13 @@ class ThermalMonitor(ProcessTaskBase): self.fan_updater = FanUpdater(chassis, self.task_stopping_event) self.temperature_updater = TemperatureUpdater(chassis, self.task_stopping_event) + self.voltage_updater = VoltageUpdater(chassis, self.task_stopping_event) def main(self): begin = time.time() self.fan_updater.update() self.temperature_updater.update() + self.voltage_updater.update() elapsed = time.time() - begin if elapsed < self.UPDATE_INTERVAL: self.wait_time = self.UPDATE_INTERVAL - elapsed From 3959352419ab274f18897db120a6f02782ff0ae4 Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Tue, 27 Jun 2023 22:39:23 -0700 Subject: [PATCH 02/10] Sensormond first commit --- sonic-sensormond/pytest.ini | 2 + sonic-sensormond/scripts/sensormond | 701 ++++++++++++++++++++++++++++ sonic-sensormond/setup.cfg | 2 + sonic-sensormond/setup.py | 44 ++ 4 files changed, 749 insertions(+) create mode 100644 sonic-sensormond/pytest.ini create mode 100755 sonic-sensormond/scripts/sensormond create mode 100644 sonic-sensormond/setup.cfg create mode 100644 sonic-sensormond/setup.py diff --git a/sonic-sensormond/pytest.ini b/sonic-sensormond/pytest.ini new file mode 100644 index 000000000..d90ee9ed9 --- /dev/null +++ b/sonic-sensormond/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv diff --git a/sonic-sensormond/scripts/sensormond b/sonic-sensormond/scripts/sensormond new file mode 100755 index 000000000..c19f2e8d2 --- /dev/null +++ b/sonic-sensormond/scripts/sensormond @@ -0,0 +1,701 @@ +#!/usr/bin/python3 + +""" + sensord + Sensor monitor daemon for SONiC +""" + +import signal +import sys +import threading +import time +from datetime import datetime + +import sonic_platform +from sonic_py_common import daemon_base, logger +from sonic_py_common.task_base import ProcessTaskBase +from swsscommon import swsscommon + + +# TODO: Once we no longer support Python 2, we can eliminate this and get the +# name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 +SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) + for n in dir(signal) if n.startswith('SIG') and '_' not in n) + +SYSLOG_IDENTIFIER = 'sensord' +NOT_AVAILABLE = 'N/A' +CHASSIS_INFO_KEY = 'chassis 1' +PHYSICAL_ENTITY_INFO_TABLE = 'PHYSICAL_ENTITY_INFO' +INVALID_SLOT = -1 + +ERR_UNKNOWN = 1 + +# Sensord daemon is designed to never exit, it must always +# return non-zero exit code when exiting and so that supervisord will +# restart it automatically. +exit_code = ERR_UNKNOWN + +# utility functions + +def try_get(callback, default=NOT_AVAILABLE): + """ + Handy function to invoke the callback and catch NotImplementedError + :param callback: Callback to be invoked + :param default: Default return value if exception occur + :return: Default return value if exception occur else return value of the callback + """ + try: + ret = callback() + if ret is None: + ret = default + except NotImplementedError: + ret = default + + return ret + + +def update_entity_info(table, parent_name, key, device, device_index): + fvs = swsscommon.FieldValuePairs( + [('position_in_parent', str(try_get(device.get_position_in_parent, device_index))), + ('parent_name', parent_name)]) + table.set(key, fvs) + + +class VoltageStatus(logger.Logger): + + def __init__(self): + super(VoltageStatus, self).__init__(SYSLOG_IDENTIFIER) + + self.voltage = None + self.over_voltage = False + self.under_voltage = False + + def set_voltage(self, name, voltage): + """ + Record voltage changes. + :param name: Name of the voltage sensor. + :param voltage: New voltage value. + :return: + """ + if voltage == NOT_AVAILABLE: + if self.voltage is not None: + self.log_warning('Voltage of {} became unavailable'.format(name)) + self.voltage = None + return + + self.voltage = voltage + + def set_over_voltage(self, voltage, threshold): + """ + Set over voltage status + :param voltage: voltage + :param threshold: High threshold + :return: True if over voltage status changed else False + """ + if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + self.log_warning('Voltage/threshold of {} became unavailable {}/{}'.format(self.name, voltage, threshold)) + old_status = self.over_voltage + self.over_voltage = False + return old_status != self.over_voltage + + status = voltage > threshold + if status == self.over_voltage: + return False + + self.over_voltage = status + return True + + def set_under_voltage(self, voltage, threshold): + """ + Set under voltage status + :param voltage: voltage + :param threshold: Low threshold + :return: True if under voltage status changed else False + """ + if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + old_status = self.under_voltage + self.under_voltage = False + return old_status != self.under_voltage + + status = voltage < threshold + if status == self.under_voltage: + return False + + self.under_voltage = status + return True + + +# +# voltageUpdater ====================================================================== +# +class VoltageUpdater(logger.Logger): + # Voltage information table name in database + VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' + + def __init__(self, chassis, task_stopping_event): + """ + Initializer of VoltageUpdater + :param chassis: Object representing a platform chassis + """ + super(VoltageUpdater, self).__init__(SYSLOG_IDENTIFIER) + + self.chassis = chassis + self.task_stopping_event = task_stopping_event + self.voltage_status_dict = {} + state_db = daemon_base.db_connect("STATE_DB") + self.table = swsscommon.Table(state_db, VoltageUpdater.VOLTAGE_INFO_TABLE_NAME) + self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) + self.chassis_table = None + + self.is_chassis_system = chassis.is_modular_chassis() + if self.is_chassis_system: + self.module_vsensors = set() + my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) + if my_slot != INVALID_SLOT: + try: + # Modular chassis does not have to have table CHASSIS_STATE_DB. + # So catch the exception here and ignore it. + table_name = VoltageUpdater.VOLTAGE_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) + except Exception as e: + self.chassis_table = None + + def __del__(self): + if self.table: + table_keys = self.table.getKeys() + for tk in table_keys: + self.table._del(tk) + if self.is_chassis_system and self.chassis_table is not None: + self.chassis_table._del(tk) + 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) + + def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): + """ + Log when any status changed + :param normal_status: Expected status. + :param normal_log: Log string for expected status. + :param abnormal_log: Log string for unexpected status + :return: + """ + if normal_status: + self.log_notice(normal_log) + else: + self.log_warning(abnormal_log) + + def update(self): + """ + Update all voltage information to database + :return: + """ + self.log_debug("Start voltage updating") + for index, voltage_sensor in enumerate(self.chassis.get_all_vsensors()): + if self.task_stopping_event.is_set(): + return + + print(index, voltage_sensor) + self._refresh_voltage_status(CHASSIS_INFO_KEY, voltage_sensor, index) + + if self.is_chassis_system: + available_vsensors = set() + for module_index, module in enumerate(self.chassis.get_all_modules()): + module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) + + for vsensor_index, vsensor in enumerate(module.get_all_vsensors()): + if self.task_stopping_event.is_set(): + return + + available_vsensors.add((vsensor, module_name, vsensor_index)) + self._refresh_voltage_status(module_name, vsensor, vsensor_index) + + vsensors_to_remove = self.module_vsensors - available_vsensors + self.module_vsensors = available_vsensors + for vsensor, parent_name, vsensor_index in vsensors_to_remove: + self._remove_vsensor_from_db(vsensor, parent_name, vsensor_index) + + self.log_debug("End Voltage updating") + + def _refresh_voltage_status(self, parent_name, vsensor, vsensor_index): + """ + Get voltage status by platform API and write to database + :param parent_name: Name of parent device of the vsensor object + :param vsensor: Object representing a platform voltage vsensor + :param vsensor_index: Index of the vsensor object in platform chassis + :return: + """ + try: + name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) + print(name) + + if name not in self.voltage_status_dict: + self.voltage_status_dict[name] = VoltageStatus() + + print(self.voltage_status_dict) + + voltage_status = self.voltage_status_dict[name] + print(voltage_status) + + high_threshold = NOT_AVAILABLE + low_threshold = NOT_AVAILABLE + high_critical_threshold = NOT_AVAILABLE + low_critical_threshold = NOT_AVAILABLE + maximum_voltage = NOT_AVAILABLE + minimum_voltage = NOT_AVAILABLE + voltage = try_get(vsensor.get_voltage) + print(voltage) + is_replaceable = try_get(vsensor.is_replaceable, False) + if voltage != NOT_AVAILABLE: + voltage_status.set_voltage(name, voltage) + minimum_voltage = try_get(vsensor.get_minimum_recorded) + maximum_voltage = try_get(vsensor.get_maximum_recorded) + high_threshold = try_get(vsensor.get_high_threshold) + low_threshold = try_get(vsensor.get_low_threshold) + high_critical_threshold = try_get(vsensor.get_high_critical_threshold) + low_critical_threshold = try_get(vsensor.get_low_critical_threshold) + + warning = False + if voltage != NOT_AVAILABLE and voltage_status.set_over_voltage(voltage, high_threshold): + self._log_on_status_changed(not voltage_status.over_voltage, + 'High voltage warning cleared: {} voltage restored to {}C, high threshold {}C'. + format(name, voltage, high_threshold), + 'High voltage warning: {} current voltage {}C, high threshold {}C'. + format(name, voltage, high_threshold) + ) + warning = warning | voltage_status.over_voltage + + if voltage != NOT_AVAILABLE and voltage_status.set_under_voltage(voltage, low_threshold): + self._log_on_status_changed(not voltage_status.under_voltage, + 'Low voltage warning cleared: {} voltage restored to {}C, low threshold {}C'. + format(name, voltage, low_threshold), + 'Low voltage warning: {} current voltage {}C, low threshold {}C'. + format(name, voltage, low_threshold) + ) + warning = warning | voltage_status.under_voltage + + fvs = swsscommon.FieldValuePairs( + [('voltage', str(voltage)), + ('minimum_voltage', str(minimum_voltage)), + ('maximum_voltage', str(maximum_voltage)), + ('high_threshold', str(high_threshold)), + ('low_threshold', str(low_threshold)), + ('warning_status', str(warning)), + ('critical_high_threshold', str(high_critical_threshold)), + ('critical_low_threshold', str(low_critical_threshold)), + ('is_replaceable', str(is_replaceable)), + ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) + ]) + + self.table.set(name, fvs) + if self.is_chassis_system and self.chassis_table is not None: + self.chassis_table.set(name, fvs) + except Exception as e: + self.log_warning('Failed to update vsensor status for {} - {}'.format(name, repr(e))) + + def _remove_vsensor_from_db(self, vsensor, parent_name, vsensor_index): + name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) + self.table._del(name) + + if self.chassis_table is not None: + self.chassis_table._del(name) + +class CurrentStatus(logger.Logger): + + def __init__(self): + super(CurrentStatus, self).__init__(SYSLOG_IDENTIFIER) + + self.current = None + self.over_current = False + self.under_current = False + + def set_current(self, name, current): + """ + Record current changes. + :param name: Name of the current sensor. + :param current: New current value. + :return: + """ + if current == NOT_AVAILABLE: + if self.current is not None: + self.log_warning('Voltage of {} became unavailable'.format(name)) + self.current = None + return + + self.current = current + + def set_over_current(self, current, threshold): + """ + Set over current status + :param current: current + :param threshold: High threshold + :return: True if over current status changed else False + """ + if current == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + self.log_warning('Voltage/threshold of {} became unavailable {}/{}'.format(self.name, current, threshold)) + old_status = self.over_current + self.over_current = False + return old_status != self.over_current + + status = current > threshold + if status == self.over_current: + return False + + self.over_current = status + return True + + def set_under_current(self, current, threshold): + """ + Set under current status + :param current: current + :param threshold: Low threshold + :return: True if under current status changed else False + """ + if current == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + old_status = self.under_current + self.under_current = False + return old_status != self.under_current + + status = current < threshold + if status == self.under_current: + return False + + self.under_current = status + return True + + +# +# currentUpdater ====================================================================== +# +class CurrentUpdater(logger.Logger): + # Current information table name in database + CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' + + def __init__(self, chassis, task_stopping_event): + """ + Initializer of CurrentUpdater + :param chassis: Object representing a platform chassis + """ + super(CurrentUpdater, self).__init__(SYSLOG_IDENTIFIER) + + self.chassis = chassis + self.task_stopping_event = task_stopping_event + self.current_status_dict = {} + state_db = daemon_base.db_connect("STATE_DB") + self.table = swsscommon.Table(state_db, CurrentUpdater.CURRENT_INFO_TABLE_NAME) + self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) + self.chassis_table = None + + self.is_chassis_system = chassis.is_modular_chassis() + if self.is_chassis_system: + self.module_isensors = set() + my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) + if my_slot != INVALID_SLOT: + try: + # Modular chassis does not have to have table CHASSIS_STATE_DB. + # So catch the exception here and ignore it. + table_name = CurrentUpdater.CURRENT_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) + except Exception as e: + self.chassis_table = None + + def __del__(self): + if self.table: + table_keys = self.table.getKeys() + for tk in table_keys: + self.table._del(tk) + if self.is_chassis_system and self.chassis_table is not None: + self.chassis_table._del(tk) + 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) + + def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): + """ + Log when any status changed + :param normal_status: Expected status. + :param normal_log: Log string for expected status. + :param abnormal_log: Log string for unexpected status + :return: + """ + if normal_status: + self.log_notice(normal_log) + else: + self.log_warning(abnormal_log) + + def update(self): + """ + Update all current information to database + :return: + """ + self.log_debug("Start current updating") + for index, current_sensor in enumerate(self.chassis.get_all_isensors()): + if self.task_stopping_event.is_set(): + return + + print(index, current_sensor) + self._refresh_current_status(CHASSIS_INFO_KEY, current_sensor, index) + + if self.is_chassis_system: + available_isensors = set() + for module_index, module in enumerate(self.chassis.get_all_modules()): + module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) + + for isensor_index, isensor in enumerate(module.get_all_isensors()): + if self.task_stopping_event.is_set(): + return + + available_isensors.add((isensor, module_name, isensor_index)) + self._refresh_current_status(module_name, isensor, isensor_index) + + isensors_to_remove = self.module_isensors - available_isensors + self.module_isensors = available_isensors + for isensor, parent_name, isensor_index in isensors_to_remove: + self._remove_isensor_from_db(isensor, parent_name, isensor_index) + + self.log_debug("End Current updating") + + def _refresh_current_status(self, parent_name, isensor, isensor_index): + """ + Get current status by platform API and write to database + :param parent_name: Name of parent device of the isensor object + :param isensor: Object representing a platform current isensor + :param isensor_index: Index of the isensor object in platform chassis + :return: + """ + try: + name = try_get(isensor.get_name, '{} isensor {}'.format(parent_name, isensor_index + 1)) + print(name) + + if name not in self.current_status_dict: + self.current_status_dict[name] = CurrentStatus() + + print(self.current_status_dict) + + current_status = self.current_status_dict[name] + print(current_status) + + high_threshold = NOT_AVAILABLE + low_threshold = NOT_AVAILABLE + high_critical_threshold = NOT_AVAILABLE + low_critical_threshold = NOT_AVAILABLE + maximum_current = NOT_AVAILABLE + minimum_current = NOT_AVAILABLE + current = try_get(isensor.get_current) + print(current) + is_replaceable = try_get(isensor.is_replaceable, False) + if current != NOT_AVAILABLE: + current_status.set_current(name, current) + minimum_current = try_get(isensor.get_minimum_recorded) + maximum_current = try_get(isensor.get_maximum_recorded) + high_threshold = try_get(isensor.get_high_threshold) + low_threshold = try_get(isensor.get_low_threshold) + high_critical_threshold = try_get(isensor.get_high_critical_threshold) + low_critical_threshold = try_get(isensor.get_low_critical_threshold) + + warning = False + if current != NOT_AVAILABLE and current_status.set_over_current(current, high_threshold): + self._log_on_status_changed(not current_status.over_current, + 'High current warning cleared: {} current restored to {}C, high threshold {}C'. + format(name, current, high_threshold), + 'High current warning: {} current current {}C, high threshold {}C'. + format(name, current, high_threshold) + ) + warning = warning | current_status.over_current + + if current != NOT_AVAILABLE and current_status.set_under_current(current, low_threshold): + self._log_on_status_changed(not current_status.under_current, + 'Low current warning cleared: {} current restored to {}C, low threshold {}C'. + format(name, current, low_threshold), + 'Low current warning: {} current current {}C, low threshold {}C'. + format(name, current, low_threshold) + ) + warning = warning | current_status.under_current + + fvs = swsscommon.FieldValuePairs( + [('current', str(current)), + ('minimum_current', str(minimum_current)), + ('maximum_current', str(maximum_current)), + ('high_threshold', str(high_threshold)), + ('low_threshold', str(low_threshold)), + ('warning_status', str(warning)), + ('critical_high_threshold', str(high_critical_threshold)), + ('critical_low_threshold', str(low_critical_threshold)), + ('is_replaceable', str(is_replaceable)), + ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) + ]) + + self.table.set(name, fvs) + if self.is_chassis_system and self.chassis_table is not None: + self.chassis_table.set(name, fvs) + except Exception as e: + self.log_warning('Failed to update isensor status for {} - {}'.format(name, repr(e))) + + def _remove_isensor_from_db(self, isensor, parent_name, isensor_index): + name = try_get(isensor.get_name, '{} isensor {}'.format(parent_name, isensor_index + 1)) + self.table._del(name) + + if self.chassis_table is not None: + self.chassis_table._del(name) + +class SensorMonitor(ProcessTaskBase): + # Initial update interval + INITIAL_INTERVAL = 5 + + # Update interval value + UPDATE_INTERVAL = 60 + + # Update elapse threshold. If update used time is larger than the value, generate a warning log. + UPDATE_ELAPSED_THRESHOLD = 30 + + def __init__(self, chassis): + """ + Initializer for SensorMonitor + :param chassis: Object representing a platform chassis + """ + super(SensorMonitor, self).__init__() + + self.wait_time = self.INITIAL_INTERVAL + + # TODO: Refactor to eliminate the need for this Logger instance + self.logger = logger.Logger(SYSLOG_IDENTIFIER) + + # Set minimum logging level to INFO + self.logger.set_min_log_priority_info() + + self.voltage_updater = VoltageUpdater(chassis, self.task_stopping_event) + self.current_updater = CurrentUpdater(chassis, self.task_stopping_event) + + def main(self): + begin = time.time() + self.voltage_updater.update() + self.current_updater.update() + elapsed = time.time() - begin + if elapsed < self.UPDATE_INTERVAL: + self.wait_time = self.UPDATE_INTERVAL - elapsed + else: + self.wait_time = self.INITIAL_INTERVAL + + if elapsed > self.UPDATE_ELAPSED_THRESHOLD: + self.logger.log_warning('Sensor update status took {} seconds, ' + 'there might be performance risk'.format(elapsed)) + + def task_worker(self): + """ + Thread function to handle Sensor status update + :return: + """ + self.logger.log_info("Start Sensor monitoring loop") + + # Start loop to update sensor info in DB periodically + while not self.task_stopping_event.wait(self.wait_time): + self.main() + + self.logger.log_info("Stop sensor monitoring loop") + + +# +# Daemon ======================================================================= +# +class SensorMonitorDaemon(daemon_base.DaemonBase): + + INTERVAL = 60 + RUN_POLICY_WARN_THRESHOLD_SECS = 30 + FAST_START_INTERVAL = 15 + + def __init__(self): + """ + Initializer of SensorMonitorDaemon + """ + super(SensorMonitorDaemon, self).__init__(SYSLOG_IDENTIFIER) + + # Set minimum logging level to INFO + self.set_min_log_priority_info() + + self.stop_event = threading.Event() + + self.wait_time = self.INTERVAL + + self.chassis = sonic_platform.platform.Platform().get_chassis() + + self.sensor_monitor = SensorMonitor(self.chassis) + self.sensor_monitor.task_run() + + def deinit(self): + """ + Deinitializer of SensorMonitorDaemon + """ + self.sensor_monitor.task_stop() + + # Override signal handler from DaemonBase + def signal_handler(self, sig, frame): + """ + Signal handler + :param sig: Signal number + :param frame: not used + :return: + """ + FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] + NONFATAL_SIGNALS = [signal.SIGHUP] + + global exit_code + + if sig in FATAL_SIGNALS: + self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) + exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us + self.sensor_monitor.task_stop() + self.stop_event.set() + elif sig in NONFATAL_SIGNALS: + self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + else: + self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + + # Main daemon logic + def run(self): + """ + Run main logical of this daemon + :return: + """ + if self.stop_event.wait(self.wait_time): + # We received a fatal signal + return False + + begin = time.time() + + interval = self.INTERVAL + elapsed = time.time() - begin + if elapsed < interval: + self.wait_time = interval - elapsed + else: + self.wait_time = self.FAST_START_INTERVAL + + if elapsed > self.RUN_POLICY_WARN_THRESHOLD_SECS: + self.log_warning('Sensor policy execution took {} seconds, ' + 'there might be performance risk'.format(elapsed)) + + return True + + +# +# Main ========================================================================= +# +def main(): + sensor_control = SensorMonitorDaemon() + + sensor_control.log_info("Starting up...") + + while sensor_control.run(): + pass + + sensor_control.log_info("Shutting down with exit code {}...".format(exit_code)) + + sensor_control.deinit() + + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/sonic-sensormond/setup.cfg b/sonic-sensormond/setup.cfg new file mode 100644 index 000000000..b7e478982 --- /dev/null +++ b/sonic-sensormond/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/sonic-sensormond/setup.py b/sonic-sensormond/setup.py new file mode 100644 index 000000000..a91c9af14 --- /dev/null +++ b/sonic-sensormond/setup.py @@ -0,0 +1,44 @@ +from setuptools import setup + +setup( + name='sonic-sensormond', + version='1.0', + description='Sensor Monitor Daemon for SONiC', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/Azure/sonic-platform-daemons', + maintainer='Mridul Bajpai' + maintainer_email='mridul@cisco.com', + packages=[ + 'tests' + ], + scripts=[ + 'scripts/sensormond', + ], + setup_requires=[ + 'pytest-runner', + 'wheel' + ], + tests_require=[ + 'mock>=2.0.0; python_version < "3.3"', + 'pytest', + 'pytest-cov', + 'sonic-platform-common' + ], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: No Input/Output (Daemon)', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.7', + 'Topic :: System :: Hardware', + ], + keywords='sonic SONiC SENSORMONITOR sensormonitor SENSORMON sensormon sensormond', + test_suite='setup.get_test_suite' +) From 850a954c43f3ea7e78c1a3180eae4ae6550734e8 Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Tue, 27 Jun 2023 22:40:33 -0700 Subject: [PATCH 03/10] Revert "Thermalctld changes for Voltage sensor monitor." This reverts commit 7b363f3d028c03d25477a4088d46ef8e1f017a8c. --- sonic-thermalctld/scripts/thermalctld | 237 -------------------------- 1 file changed, 237 deletions(-) diff --git a/sonic-thermalctld/scripts/thermalctld b/sonic-thermalctld/scripts/thermalctld index 6690e1f45..806bf5413 100644 --- a/sonic-thermalctld/scripts/thermalctld +++ b/sonic-thermalctld/scripts/thermalctld @@ -731,241 +731,6 @@ class TemperatureUpdater(logger.Logger): self.chassis_table._del(name) -class VoltageStatus(logger.Logger): - - def __init__(self): - super(VoltageStatus, self).__init__(SYSLOG_IDENTIFIER) - - self.voltage = None - self.over_voltage = False - self.under_voltage = False - - def set_voltage(self, name, voltage): - """ - Record voltage changes. - :param name: Name of the voltage sensor. - :param voltage: New voltage value. - :return: - """ - if voltage == NOT_AVAILABLE: - if self.voltage is not None: - self.log_warning('Voltage of {} became unavailable'.format(name)) - self.voltage = None - return - - self.voltage = voltage - - def set_over_voltage(self, voltage, threshold): - """ - Set over voltage status - :param voltage: voltage - :param threshold: High threshold - :return: True if over voltage status changed else False - """ - if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - self.log_warning('Voltage/threshold of {} became unavailable {}/{}'.format(self.name, voltage, threshold)) - old_status = self.over_voltage - self.over_voltage = False - return old_status != self.over_voltage - - status = voltage > threshold - if status == self.over_voltage: - return False - - self.over_voltage = status - return True - - def set_under_voltage(self, voltage, threshold): - """ - Set under voltage status - :param voltage: voltage - :param threshold: Low threshold - :return: True if under voltage status changed else False - """ - if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - old_status = self.under_voltage - self.under_voltage = False - return old_status != self.under_voltage - - status = voltage < threshold - if status == self.under_voltage: - return False - - self.under_voltage = status - return True - - -# -# VoltageUpdater ====================================================================== -# -class VoltageUpdater(logger.Logger): - # Voltage information table name in database - VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' - - def __init__(self, chassis, task_stopping_event): - """ - Initializer of VoltageUpdater - :param chassis: Object representing a platform chassis - """ - super(VoltageUpdater, self).__init__(SYSLOG_IDENTIFIER) - - self.chassis = chassis - self.task_stopping_event = task_stopping_event - self.voltage_status_dict = {} - state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, VoltageUpdater.VOLTAGE_INFO_TABLE_NAME) - self.chassis_table = None - - self.is_chassis_system = chassis.is_modular_chassis() - if self.is_chassis_system: - self.module_vsensors = set() - my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) - if my_slot != INVALID_SLOT: - try: - # Modular chassis does not have to have table CHASSIS_STATE_DB. - # So catch the exception here and ignore it. - table_name = VoltageUpdater.VOLTAGE_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) - except Exception as e: - self.chassis_table = None - - def __del__(self): - if self.table: - table_keys = self.table.getKeys() - for tk in table_keys: - self.table._del(tk) - if self.is_chassis_system and self.chassis_table is not None: - self.chassis_table._del(tk) - - def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): - """ - Log when any status changed - :param normal_status: Expected status. - :param normal_log: Log string for expected status. - :param abnormal_log: Log string for unexpected status - :return: - """ - if normal_status: - self.log_notice(normal_log) - else: - self.log_warning(abnormal_log) - - def update(self): - """ - Update all voltage information to database - :return: - """ - self.log_debug("Start voltage updating") - for index, voltage_sensor in enumerate(self.chassis.get_all_vsensors()): - if self.task_stopping_event.is_set(): - return - - print(index, voltage_sensor) - self._refresh_voltage_status(CHASSIS_INFO_KEY, voltage_sensor, index) - - if self.is_chassis_system: - available_vsensors = set() - for module_index, module in enumerate(self.chassis.get_all_modules()): - module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) - - for vsensor_index, vsensor in enumerate(module.get_all_vsensors()): - if self.task_stopping_event.is_set(): - return - - available_vsensors.add((vsensor, module_name, vsensor_index)) - self._refresh_voltage_status(module_name, vsensor, vsensor_index) - - vsensors_to_remove = self.module_vsensors - available_vsensors - self.module_vsensors = available_vsensors - for vsensor, parent_name, vsensor_index in vsensors_to_remove: - self._remove_vsensor_from_db(vsensor, parent_name, vsensor_index) - - self.log_debug("End Voltage updating") - - def _refresh_voltage_status(self, parent_name, vsensor, vsensor_index): - """ - Get voltage status by platform API and write to database - :param parent_name: Name of parent device of the vsensor object - :param vsensor: Object representing a platform voltage vsensor - :param vsensor_index: Index of the vsensor object in platform chassis - :return: - """ - try: - name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) - print(name) - - if name not in self.voltage_status_dict: - self.voltage_status_dict[name] = VoltageStatus() - - print(self.voltage_status_dict) - - voltage_status = self.voltage_status_dict[name] - print(voltage_status) - - high_threshold = NOT_AVAILABLE - low_threshold = NOT_AVAILABLE - high_critical_threshold = NOT_AVAILABLE - low_critical_threshold = NOT_AVAILABLE - maximum_voltage = NOT_AVAILABLE - minimum_voltage = NOT_AVAILABLE - voltage = try_get(vsensor.get_voltage) - print(voltage) - is_replaceable = try_get(vsensor.is_replaceable, False) - if voltage != NOT_AVAILABLE: - voltage_status.set_voltage(name, voltage) - minimum_voltage = try_get(vsensor.get_minimum_recorded) - maximum_voltage = try_get(vsensor.get_maximum_recorded) - high_threshold = try_get(vsensor.get_high_threshold) - low_threshold = try_get(vsensor.get_low_threshold) - high_critical_threshold = try_get(vsensor.get_high_critical_threshold) - low_critical_threshold = try_get(vsensor.get_low_critical_threshold) - - warning = False - if voltage != NOT_AVAILABLE and voltage_status.set_over_voltage(voltage, high_threshold): - self._log_on_status_changed(not voltage_status.over_voltage, - 'High voltage warning cleared: {} voltage restored to {}C, high threshold {}C'. - format(name, voltage, high_threshold), - 'High voltage warning: {} current voltage {}C, high threshold {}C'. - format(name, voltage, high_threshold) - ) - warning = warning | voltage_status.over_voltage - - if voltage != NOT_AVAILABLE and voltage_status.set_under_voltage(voltage, low_threshold): - self._log_on_status_changed(not voltage_status.under_voltage, - 'Low voltage warning cleared: {} voltage restored to {}C, low threshold {}C'. - format(name, voltage, low_threshold), - 'Low voltage warning: {} current voltage {}C, low threshold {}C'. - format(name, voltage, low_threshold) - ) - warning = warning | voltage_status.under_voltage - - fvs = swsscommon.FieldValuePairs( - [('voltage', str(voltage)), - ('minimum_voltage', str(minimum_voltage)), - ('maximum_voltage', str(maximum_voltage)), - ('high_threshold', str(high_threshold)), - ('low_threshold', str(low_threshold)), - ('warning_status', str(warning)), - ('critical_high_threshold', str(high_critical_threshold)), - ('critical_low_threshold', str(low_critical_threshold)), - ('is_replaceable', str(is_replaceable)), - ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) - ]) - - self.table.set(name, fvs) - if self.is_chassis_system and self.chassis_table is not None: - self.chassis_table.set(name, fvs) - except Exception as e: - self.log_warning('Failed to update vsensor status for {} - {}'.format(name, repr(e))) - - def _remove_vsensor_from_db(self, vsensor, parent_name, vsensor_index): - name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) - self.table._del(name) - - if self.chassis_table is not None: - self.chassis_table._del(name) - class ThermalMonitor(ProcessTaskBase): # Initial update interval INITIAL_INTERVAL = 5 @@ -993,13 +758,11 @@ class ThermalMonitor(ProcessTaskBase): self.fan_updater = FanUpdater(chassis, self.task_stopping_event) self.temperature_updater = TemperatureUpdater(chassis, self.task_stopping_event) - self.voltage_updater = VoltageUpdater(chassis, self.task_stopping_event) def main(self): begin = time.time() self.fan_updater.update() self.temperature_updater.update() - self.voltage_updater.update() elapsed = time.time() - begin if elapsed < self.UPDATE_INTERVAL: self.wait_time = self.UPDATE_INTERVAL - elapsed From ef3c59719a13753655b14c035fd360094e555e8a Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Wed, 26 Jul 2023 01:17:18 -0700 Subject: [PATCH 04/10] Consolidated code --- azure-pipelines.yml | 3 + sonic-sensormond/scripts/sensormond | 358 +++++++++------------------- 2 files changed, 111 insertions(+), 250 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8895afe26..3d64f2199 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -53,6 +53,9 @@ parameters: - name: ycabled root_dir: sonic-ycabled python3: true + - name: sensormond + root_dir: sonic-sensormond + python3: true - name: artifactBranch type: string default: 'refs/heads/master' diff --git a/sonic-sensormond/scripts/sensormond b/sonic-sensormond/scripts/sensormond index c19f2e8d2..06199c878 100755 --- a/sonic-sensormond/scripts/sensormond +++ b/sonic-sensormond/scripts/sensormond @@ -1,7 +1,7 @@ #!/usr/bin/python3 """ - sensord + sensormond Sensor monitor daemon for SONiC """ @@ -17,25 +17,21 @@ from sonic_py_common.task_base import ProcessTaskBase from swsscommon import swsscommon -# TODO: Once we no longer support Python 2, we can eliminate this and get the -# name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 -SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) - for n in dir(signal) if n.startswith('SIG') and '_' not in n) +SYSLOG_IDENTIFIER = 'sensormond' +NOT_AVAILABLE = 'N/A' +CHASSIS_INFO_KEY = 'chassis 1' +INVALID_SLOT = -1 -SYSLOG_IDENTIFIER = 'sensord' -NOT_AVAILABLE = 'N/A' -CHASSIS_INFO_KEY = 'chassis 1' PHYSICAL_ENTITY_INFO_TABLE = 'PHYSICAL_ENTITY_INFO' -INVALID_SLOT = -1 -ERR_UNKNOWN = 1 +# Python2 support. +SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) + for n in dir(signal) if n.startswith('SIG') and '_' not in n) -# Sensord daemon is designed to never exit, it must always -# return non-zero exit code when exiting and so that supervisord will -# restart it automatically. -exit_code = ERR_UNKNOWN +# Exit with non-zero exit code so supervisord will restart Sensormon. +exit_code = 1 -# utility functions +# Utility functions def try_get(callback, default=NOT_AVAILABLE): """ @@ -53,75 +49,72 @@ def try_get(callback, default=NOT_AVAILABLE): return ret - def update_entity_info(table, parent_name, key, device, device_index): fvs = swsscommon.FieldValuePairs( [('position_in_parent', str(try_get(device.get_position_in_parent, device_index))), ('parent_name', parent_name)]) table.set(key, fvs) - -class VoltageStatus(logger.Logger): +class SensorStatus(logger.Logger): def __init__(self): - super(VoltageStatus, self).__init__(SYSLOG_IDENTIFIER) + super(SensorStatus, self).__init__(SYSLOG_IDENTIFIER) - self.voltage = None - self.over_voltage = False - self.under_voltage = False + self.value = None + self.over_threshold = False + self.under_threshold = False - def set_voltage(self, name, voltage): + def set_value(self, name, value): """ - Record voltage changes. - :param name: Name of the voltage sensor. - :param voltage: New voltage value. - :return: + Record sensor changes. + :param name: Name of the sensor. + :param value: New value. """ - if voltage == NOT_AVAILABLE: - if self.voltage is not None: - self.log_warning('Voltage of {} became unavailable'.format(name)) - self.voltage = None + if value == NOT_AVAILABLE: + if self.value is not None: + self.log_warning('Value of {} became unavailable'.format(name)) + self.value = None return - self.voltage = voltage + self.value = value - def set_over_voltage(self, voltage, threshold): + def set_over_threshold(self, value, threshold): """ - Set over voltage status - :param voltage: voltage + Set over threshold status + :param value: value :param threshold: High threshold - :return: True if over voltage status changed else False + :return: True if over threshold status changed else False """ - if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - self.log_warning('Voltage/threshold of {} became unavailable {}/{}'.format(self.name, voltage, threshold)) - old_status = self.over_voltage - self.over_voltage = False - return old_status != self.over_voltage - - status = voltage > threshold - if status == self.over_voltage: + if value == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + self.log_warning('Value/threshold of {} became unavailable {}/{}'.format(self.name, value, threshold)) + old_status = self.over_threshold + self.over_threshold = False + return old_status != self.over_threshold + + status = value > threshold + if status == self.over_threshold: return False - self.over_voltage = status + self.over_threshold = status return True - def set_under_voltage(self, voltage, threshold): + def set_under_threshold(self, value, threshold): """ - Set under voltage status - :param voltage: voltage + Set under value status + :param value: value :param threshold: Low threshold - :return: True if under voltage status changed else False + :return: True if under threshold status changed else False """ - if voltage == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - old_status = self.under_voltage - self.under_voltage = False - return old_status != self.under_voltage + if value == NOT_AVAILABLE or threshold == NOT_AVAILABLE: + old_status = self.under_threshold + self.under_threshold = False + return old_status != self.under_threshold - status = voltage < threshold - if status == self.under_voltage: + status = value < threshold + if status == self.under_threshold: return False - self.under_voltage = status + self.under_threshold = status return True @@ -132,7 +125,7 @@ class VoltageUpdater(logger.Logger): # Voltage information table name in database VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' - def __init__(self, chassis, task_stopping_event): + def __init__(self, chassis): """ Initializer of VoltageUpdater :param chassis: Object representing a platform chassis @@ -140,7 +133,6 @@ class VoltageUpdater(logger.Logger): super(VoltageUpdater, self).__init__(SYSLOG_IDENTIFIER) self.chassis = chassis - self.task_stopping_event = task_stopping_event self.voltage_status_dict = {} state_db = daemon_base.db_connect("STATE_DB") self.table = swsscommon.Table(state_db, VoltageUpdater.VOLTAGE_INFO_TABLE_NAME) @@ -153,8 +145,7 @@ class VoltageUpdater(logger.Logger): my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) if my_slot != INVALID_SLOT: try: - # Modular chassis does not have to have table CHASSIS_STATE_DB. - # So catch the exception here and ignore it. + # Modular chassis may not have table CHASSIS_STATE_DB. table_name = VoltageUpdater.VOLTAGE_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) @@ -191,10 +182,8 @@ class VoltageUpdater(logger.Logger): Update all voltage information to database :return: """ - self.log_debug("Start voltage updating") + self.log_debug("Start voltage update") for index, voltage_sensor in enumerate(self.chassis.get_all_vsensors()): - if self.task_stopping_event.is_set(): - return print(index, voltage_sensor) self._refresh_voltage_status(CHASSIS_INFO_KEY, voltage_sensor, index) @@ -205,9 +194,6 @@ class VoltageUpdater(logger.Logger): module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) for vsensor_index, vsensor in enumerate(module.get_all_vsensors()): - if self.task_stopping_event.is_set(): - return - available_vsensors.add((vsensor, module_name, vsensor_index)) self._refresh_voltage_status(module_name, vsensor, vsensor_index) @@ -230,13 +216,12 @@ class VoltageUpdater(logger.Logger): name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) print(name) - if name not in self.voltage_status_dict: - self.voltage_status_dict[name] = VoltageStatus() + update_entity_info(self.phy_entity_table, parent_name, name, vsensor, vsensor_index + 1) - print(self.voltage_status_dict) + if name not in self.voltage_status_dict: + self.voltage_status_dict[name] = SensorStatus() voltage_status = self.voltage_status_dict[name] - print(voltage_status) high_threshold = NOT_AVAILABLE low_threshold = NOT_AVAILABLE @@ -244,11 +229,11 @@ class VoltageUpdater(logger.Logger): low_critical_threshold = NOT_AVAILABLE maximum_voltage = NOT_AVAILABLE minimum_voltage = NOT_AVAILABLE - voltage = try_get(vsensor.get_voltage) - print(voltage) + voltage = try_get(vsensor.get_value) + print("Voltage Read: ", voltage) is_replaceable = try_get(vsensor.is_replaceable, False) if voltage != NOT_AVAILABLE: - voltage_status.set_voltage(name, voltage) + voltage_status.set_value(name, voltage) minimum_voltage = try_get(vsensor.get_minimum_recorded) maximum_voltage = try_get(vsensor.get_maximum_recorded) high_threshold = try_get(vsensor.get_high_threshold) @@ -257,23 +242,23 @@ class VoltageUpdater(logger.Logger): low_critical_threshold = try_get(vsensor.get_low_critical_threshold) warning = False - if voltage != NOT_AVAILABLE and voltage_status.set_over_voltage(voltage, high_threshold): - self._log_on_status_changed(not voltage_status.over_voltage, - 'High voltage warning cleared: {} voltage restored to {}C, high threshold {}C'. + if voltage != NOT_AVAILABLE and voltage_status.set_over_threshold(voltage, high_threshold): + self._log_on_status_changed(not voltage_status.over_threshold, + 'High voltage warning cleared: {} voltage restored to {}mV, high threshold {}mV'. format(name, voltage, high_threshold), - 'High voltage warning: {} current voltage {}C, high threshold {}C'. + 'High voltage warning: {} current voltage {}mV, high threshold {}mV'. format(name, voltage, high_threshold) ) - warning = warning | voltage_status.over_voltage + warning = warning | voltage_status.over_threshold - if voltage != NOT_AVAILABLE and voltage_status.set_under_voltage(voltage, low_threshold): - self._log_on_status_changed(not voltage_status.under_voltage, - 'Low voltage warning cleared: {} voltage restored to {}C, low threshold {}C'. + if voltage != NOT_AVAILABLE and voltage_status.set_under_threshold(voltage, low_threshold): + self._log_on_status_changed(not voltage_status.under_threshold, + 'Low voltage warning cleared: {} voltage restored to {}mV, low threshold {}mV'. format(name, voltage, low_threshold), - 'Low voltage warning: {} current voltage {}C, low threshold {}C'. + 'Low voltage warning: {} current voltage {}mV, low threshold {}mV'. format(name, voltage, low_threshold) ) - warning = warning | voltage_status.under_voltage + warning = warning | voltage_status.under_threshold fvs = swsscommon.FieldValuePairs( [('voltage', str(voltage)), @@ -300,71 +285,7 @@ class VoltageUpdater(logger.Logger): if self.chassis_table is not None: self.chassis_table._del(name) - -class CurrentStatus(logger.Logger): - - def __init__(self): - super(CurrentStatus, self).__init__(SYSLOG_IDENTIFIER) - - self.current = None - self.over_current = False - self.under_current = False - - def set_current(self, name, current): - """ - Record current changes. - :param name: Name of the current sensor. - :param current: New current value. - :return: - """ - if current == NOT_AVAILABLE: - if self.current is not None: - self.log_warning('Voltage of {} became unavailable'.format(name)) - self.current = None - return - - self.current = current - - def set_over_current(self, current, threshold): - """ - Set over current status - :param current: current - :param threshold: High threshold - :return: True if over current status changed else False - """ - if current == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - self.log_warning('Voltage/threshold of {} became unavailable {}/{}'.format(self.name, current, threshold)) - old_status = self.over_current - self.over_current = False - return old_status != self.over_current - - status = current > threshold - if status == self.over_current: - return False - - self.over_current = status - return True - - def set_under_current(self, current, threshold): - """ - Set under current status - :param current: current - :param threshold: Low threshold - :return: True if under current status changed else False - """ - if current == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - old_status = self.under_current - self.under_current = False - return old_status != self.under_current - - status = current < threshold - if status == self.under_current: - return False - - self.under_current = status - return True - - + # # currentUpdater ====================================================================== # @@ -372,7 +293,7 @@ class CurrentUpdater(logger.Logger): # Current information table name in database CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' - def __init__(self, chassis, task_stopping_event): + def __init__(self, chassis): """ Initializer of CurrentUpdater :param chassis: Object representing a platform chassis @@ -380,7 +301,6 @@ class CurrentUpdater(logger.Logger): super(CurrentUpdater, self).__init__(SYSLOG_IDENTIFIER) self.chassis = chassis - self.task_stopping_event = task_stopping_event self.current_status_dict = {} state_db = daemon_base.db_connect("STATE_DB") self.table = swsscommon.Table(state_db, CurrentUpdater.CURRENT_INFO_TABLE_NAME) @@ -393,8 +313,7 @@ class CurrentUpdater(logger.Logger): my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) if my_slot != INVALID_SLOT: try: - # Modular chassis does not have to have table CHASSIS_STATE_DB. - # So catch the exception here and ignore it. + # Modular chassis may not have table CHASSIS_STATE_DB. table_name = CurrentUpdater.CURRENT_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) @@ -433,8 +352,6 @@ class CurrentUpdater(logger.Logger): """ self.log_debug("Start current updating") for index, current_sensor in enumerate(self.chassis.get_all_isensors()): - if self.task_stopping_event.is_set(): - return print(index, current_sensor) self._refresh_current_status(CHASSIS_INFO_KEY, current_sensor, index) @@ -445,9 +362,6 @@ class CurrentUpdater(logger.Logger): module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) for isensor_index, isensor in enumerate(module.get_all_isensors()): - if self.task_stopping_event.is_set(): - return - available_isensors.add((isensor, module_name, isensor_index)) self._refresh_current_status(module_name, isensor, isensor_index) @@ -470,13 +384,12 @@ class CurrentUpdater(logger.Logger): name = try_get(isensor.get_name, '{} isensor {}'.format(parent_name, isensor_index + 1)) print(name) - if name not in self.current_status_dict: - self.current_status_dict[name] = CurrentStatus() + update_entity_info(self.phy_entity_table, parent_name, name, isensor, isensor_index + 1) - print(self.current_status_dict) + if name not in self.current_status_dict: + self.current_status_dict[name] = SensorStatus() current_status = self.current_status_dict[name] - print(current_status) high_threshold = NOT_AVAILABLE low_threshold = NOT_AVAILABLE @@ -484,11 +397,11 @@ class CurrentUpdater(logger.Logger): low_critical_threshold = NOT_AVAILABLE maximum_current = NOT_AVAILABLE minimum_current = NOT_AVAILABLE - current = try_get(isensor.get_current) - print(current) + current = try_get(isensor.get_value) + print("Current Read: ", current) is_replaceable = try_get(isensor.is_replaceable, False) if current != NOT_AVAILABLE: - current_status.set_current(name, current) + current_status.set_value(name, current) minimum_current = try_get(isensor.get_minimum_recorded) maximum_current = try_get(isensor.get_maximum_recorded) high_threshold = try_get(isensor.get_high_threshold) @@ -497,23 +410,23 @@ class CurrentUpdater(logger.Logger): low_critical_threshold = try_get(isensor.get_low_critical_threshold) warning = False - if current != NOT_AVAILABLE and current_status.set_over_current(current, high_threshold): - self._log_on_status_changed(not current_status.over_current, - 'High current warning cleared: {} current restored to {}C, high threshold {}C'. + if current != NOT_AVAILABLE and current_status.set_over_threshold(current, high_threshold): + self._log_on_status_changed(not current_status.over_threshold, + 'High current warning cleared: {} current restored to {}mA, high threshold {}mA'. format(name, current, high_threshold), - 'High current warning: {} current current {}C, high threshold {}C'. + 'High current warning: {} current current {}mA, high threshold {}mA'. format(name, current, high_threshold) ) - warning = warning | current_status.over_current + warning = warning | current_status.over_threshold - if current != NOT_AVAILABLE and current_status.set_under_current(current, low_threshold): - self._log_on_status_changed(not current_status.under_current, - 'Low current warning cleared: {} current restored to {}C, low threshold {}C'. + if current != NOT_AVAILABLE and current_status.set_under_threshold(current, low_threshold): + self._log_on_status_changed(not current_status.under_threshold, + 'Low current warning cleared: {} current restored to {}mA, low threshold {}mA'. format(name, current, low_threshold), - 'Low current warning: {} current current {}C, low threshold {}C'. + 'Low current warning: {} current current {}mA, low threshold {}mA'. format(name, current, low_threshold) ) - warning = warning | current_status.under_current + warning = warning | current_status.under_threshold fvs = swsscommon.FieldValuePairs( [('current', str(current)), @@ -541,70 +454,17 @@ class CurrentUpdater(logger.Logger): if self.chassis_table is not None: self.chassis_table._del(name) -class SensorMonitor(ProcessTaskBase): - # Initial update interval - INITIAL_INTERVAL = 5 - - # Update interval value - UPDATE_INTERVAL = 60 - - # Update elapse threshold. If update used time is larger than the value, generate a warning log. - UPDATE_ELAPSED_THRESHOLD = 30 - - def __init__(self, chassis): - """ - Initializer for SensorMonitor - :param chassis: Object representing a platform chassis - """ - super(SensorMonitor, self).__init__() - - self.wait_time = self.INITIAL_INTERVAL - - # TODO: Refactor to eliminate the need for this Logger instance - self.logger = logger.Logger(SYSLOG_IDENTIFIER) - - # Set minimum logging level to INFO - self.logger.set_min_log_priority_info() - - self.voltage_updater = VoltageUpdater(chassis, self.task_stopping_event) - self.current_updater = CurrentUpdater(chassis, self.task_stopping_event) - - def main(self): - begin = time.time() - self.voltage_updater.update() - self.current_updater.update() - elapsed = time.time() - begin - if elapsed < self.UPDATE_INTERVAL: - self.wait_time = self.UPDATE_INTERVAL - elapsed - else: - self.wait_time = self.INITIAL_INTERVAL - - if elapsed > self.UPDATE_ELAPSED_THRESHOLD: - self.logger.log_warning('Sensor update status took {} seconds, ' - 'there might be performance risk'.format(elapsed)) - - def task_worker(self): - """ - Thread function to handle Sensor status update - :return: - """ - self.logger.log_info("Start Sensor monitoring loop") - - # Start loop to update sensor info in DB periodically - while not self.task_stopping_event.wait(self.wait_time): - self.main() - - self.logger.log_info("Stop sensor monitoring loop") - - # # Daemon ======================================================================= # class SensorMonitorDaemon(daemon_base.DaemonBase): - INTERVAL = 60 - RUN_POLICY_WARN_THRESHOLD_SECS = 30 - FAST_START_INTERVAL = 15 + # Initial update interval + INITIAL_INTERVAL = 5 + # Periodic Update interval + UPDATE_INTERVAL = 60 + # Update time threshold. If update time exceeds this threshold, log warning msg. + UPDATE_ELAPSED_THRESHOLD = 30 def __init__(self): """ @@ -617,18 +477,16 @@ class SensorMonitorDaemon(daemon_base.DaemonBase): self.stop_event = threading.Event() - self.wait_time = self.INTERVAL + self.wait_time = self.INITIAL_INTERVAL + + self.interval = self.UPDATE_INTERVAL self.chassis = sonic_platform.platform.Platform().get_chassis() - self.sensor_monitor = SensorMonitor(self.chassis) - self.sensor_monitor.task_run() + self.voltage_updater = VoltageUpdater(self.chassis) + + self.current_updater = CurrentUpdater(self.chassis) - def deinit(self): - """ - Deinitializer of SensorMonitorDaemon - """ - self.sensor_monitor.task_stop() # Override signal handler from DaemonBase def signal_handler(self, sig, frame): @@ -646,7 +504,6 @@ class SensorMonitorDaemon(daemon_base.DaemonBase): if sig in FATAL_SIGNALS: self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us - self.sensor_monitor.task_stop() self.stop_event.set() elif sig in NONFATAL_SIGNALS: self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) @@ -665,20 +522,21 @@ class SensorMonitorDaemon(daemon_base.DaemonBase): begin = time.time() - interval = self.INTERVAL + self.voltage_updater.update() + self.current_updater.update() + elapsed = time.time() - begin - if elapsed < interval: - self.wait_time = interval - elapsed + if elapsed < self.interval: + self.wait_time = self.interval - elapsed else: - self.wait_time = self.FAST_START_INTERVAL + self.wait_time = self.INITIAL_INTERVAL - if elapsed > self.RUN_POLICY_WARN_THRESHOLD_SECS: - self.log_warning('Sensor policy execution took {} seconds, ' - 'there might be performance risk'.format(elapsed)) + if elapsed > self.UPDATE_ELAPSED_THRESHOLD: + self.logger.log_warning('Sensors update took a long time : ' + '{} seconds'.format(elapsed)) return True - # # Main ========================================================================= # From 3252f7837a9a6e8a31a5178fbc3cb1b6250aeb72 Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Wed, 2 Aug 2023 09:55:59 -0700 Subject: [PATCH 05/10] Added Unit test --- sonic-sensormond/scripts/sensormond | 12 +- sonic-sensormond/setup.py | 4 +- sonic-sensormond/tests/__init__.py | 0 sonic-sensormond/tests/mock_platform.py | 661 ++++++++++++++++++ sonic-sensormond/tests/mock_swsscommon.py | 34 + .../mocked_libs/sonic_platform/__init__.py | 6 + .../mocked_libs/sonic_platform/chassis.py | 25 + .../mocked_libs/sonic_platform/platform.py | 11 + .../tests/mocked_libs/swsscommon/__init__.py | 5 + .../mocked_libs/swsscommon/swsscommon.py | 56 ++ sonic-sensormond/tests/test_sensormond.py | 499 +++++++++++++ 11 files changed, 1306 insertions(+), 7 deletions(-) create mode 100644 sonic-sensormond/tests/__init__.py create mode 100644 sonic-sensormond/tests/mock_platform.py create mode 100644 sonic-sensormond/tests/mock_swsscommon.py create mode 100644 sonic-sensormond/tests/mocked_libs/sonic_platform/__init__.py create mode 100644 sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py create mode 100644 sonic-sensormond/tests/mocked_libs/sonic_platform/platform.py create mode 100644 sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py create mode 100644 sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py create mode 100644 sonic-sensormond/tests/test_sensormond.py diff --git a/sonic-sensormond/scripts/sensormond b/sonic-sensormond/scripts/sensormond index 06199c878..ad3765485 100755 --- a/sonic-sensormond/scripts/sensormond +++ b/sonic-sensormond/scripts/sensormond @@ -86,7 +86,6 @@ class SensorStatus(logger.Logger): :return: True if over threshold status changed else False """ if value == NOT_AVAILABLE or threshold == NOT_AVAILABLE: - self.log_warning('Value/threshold of {} became unavailable {}/{}'.format(self.name, value, threshold)) old_status = self.over_threshold self.over_threshold = False return old_status != self.over_threshold @@ -342,8 +341,12 @@ class CurrentUpdater(logger.Logger): """ if normal_status: self.log_notice(normal_log) + print(normal_log) else: self.log_warning(abnormal_log) + print(abnormal_log) + print(vars(self)) + print(sys.path) def update(self): """ @@ -410,11 +413,12 @@ class CurrentUpdater(logger.Logger): low_critical_threshold = try_get(isensor.get_low_critical_threshold) warning = False + print(current_status) if current != NOT_AVAILABLE and current_status.set_over_threshold(current, high_threshold): self._log_on_status_changed(not current_status.over_threshold, - 'High current warning cleared: {} current restored to {}mA, high threshold {}mA'. + 'High Current warning cleared: {} current restored to {}mA, high threshold {}mA'. format(name, current, high_threshold), - 'High current warning: {} current current {}mA, high threshold {}mA'. + 'High Current warning: {} current Current {}mA, high threshold {}mA'. format(name, current, high_threshold) ) warning = warning | current_status.over_threshold @@ -550,8 +554,6 @@ def main(): sensor_control.log_info("Shutting down with exit code {}...".format(exit_code)) - sensor_control.deinit() - return exit_code diff --git a/sonic-sensormond/setup.py b/sonic-sensormond/setup.py index a91c9af14..a9dc038ff 100644 --- a/sonic-sensormond/setup.py +++ b/sonic-sensormond/setup.py @@ -3,12 +3,12 @@ setup( name='sonic-sensormond', version='1.0', - description='Sensor Monitor Daemon for SONiC', + description='Sensor Monitor daemon for SONiC', license='Apache 2.0', author='SONiC Team', author_email='linuxnetdev@microsoft.com', url='https://github.com/Azure/sonic-platform-daemons', - maintainer='Mridul Bajpai' + maintainer='Mridul Bajpai', maintainer_email='mridul@cisco.com', packages=[ 'tests' diff --git a/sonic-sensormond/tests/__init__.py b/sonic-sensormond/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sonic-sensormond/tests/mock_platform.py b/sonic-sensormond/tests/mock_platform.py new file mode 100644 index 000000000..a09fd207b --- /dev/null +++ b/sonic-sensormond/tests/mock_platform.py @@ -0,0 +1,661 @@ +from sonic_platform_base import chassis_base +from sonic_platform_base import fan_base +from sonic_platform_base import fan_drawer_base +from sonic_platform_base import module_base +from sonic_platform_base import psu_base +from sonic_platform_base import sfp_base +from sonic_platform_base import thermal_base +from sonic_platform_base import sensor_base +from sonic_platform_base.sonic_thermal_control import thermal_manager_base + + +class MockFan(fan_base.FanBase): + def __init__(self): + super(MockFan, self).__init__() + self._name = None + self._presence = True + self._model = 'Fan Model' + self._serial = 'Fan Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = True + + self._speed = 20 + self._speed_tolerance = 20 + self._target_speed = 20 + self._direction = self.FAN_DIRECTION_INTAKE + self._status_led = self.STATUS_LED_COLOR_RED + + def get_speed(self): + return self._speed + + def get_speed_tolerance(self): + return self._speed_tolerance + + def get_target_speed(self): + return self._target_speed + + def get_direction(self): + return self._direction + + def get_status_led(self): + return self._status_led + + def set_status_led(self, value): + self._status_led = value + + def make_under_speed(self): + self._speed = 1 + self._target_speed = 2 + self._speed_tolerance = 0 + + def make_over_speed(self): + self._speed = 2 + self._target_speed = 1 + self._speed_tolerance = 0 + + def make_normal_speed(self): + self._speed = 1 + self._target_speed = 1 + self._speed_tolerance = 0 + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockErrorFan(MockFan): + def get_speed(self): + raise Exception('Failed to get speed') + + +class MockFanDrawer(fan_drawer_base.FanDrawerBase): + def __init__(self, index): + super(MockFanDrawer, self).__init__() + self._name = 'FanDrawer {}'.format(index) + self._presence = True + self._model = 'Fan Drawer Model' + self._serial = 'Fan Drawer Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = True + + self._status_led = self.STATUS_LED_COLOR_RED + + def get_all_fans(self): + return self._fan_list + + def get_status_led(self): + return self._status_led + + def set_status_led(self, value): + self._status_led = value + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockPsu(psu_base.PsuBase): + def __init__(self): + super(MockPsu, self).__init__() + self._name = None + self._presence = True + self._model = 'PSU Model' + self._serial = 'PSU Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = True + + def get_all_fans(self): + return self._fan_list + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockSfp(sfp_base.SfpBase): + def __init__(self): + super(MockSfp, self).__init__() + self._name = None + self._presence = True + self._model = 'SFP Model' + self._serial = 'SFP Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = True + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + + +class MockVsensor(sensor_base.VsensorBase): + def __init__(self, index=None): + super(MockVsensor, self).__init__() + self._name = 'Vsensor {}'.format(index) if index != None else None + self._presence = True + self._model = 'Vsensor Model' + self._serial = 'Vsensor Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = False + + self._value = 2 + self._minimum_value = 1 + self._maximum_value = 5 + self._high_threshold = 3 + self._low_threshold = 1 + self._high_critical_threshold = 4 + self._low_critical_threshold = 0 + + def get_value(self): + return self._value + + def get_minimum_recorded(self): + return self._minimum_value + + def get_maximum_recorded(self): + return self._maximum_value + + def get_high_threshold(self): + return self._high_threshold + + def get_low_threshold(self): + return self._low_threshold + + def get_high_critical_threshold(self): + return self._high_critical_threshold + + def get_low_critical_threshold(self): + return self._low_critical_threshold + + def make_over_threshold(self): + self._high_threshold = 2 + self._value = 3 + self._low_threshold = 1 + + def make_under_threshold(self): + self._high_threshold = 3 + self._value = 1 + self._low_threshold = 2 + + def make_normal_value(self): + self._high_threshold = 3 + self._value = 2 + self._low_threshold = 1 + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + +class MockIsensor(sensor_base.IsensorBase): + def __init__(self, index=None): + super(MockIsensor, self).__init__() + self._name = 'Isensor {}'.format(index) if index != None else None + self._presence = True + self._model = 'Isensor Model' + self._serial = 'Isensor Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = False + + self._value = 2 + self._minimum_value = 1 + self._maximum_value = 5 + self._high_threshold = 3 + self._low_threshold = 1 + self._high_critical_threshold = 4 + self._low_critical_threshold = 0 + + def get_value(self): + return self._value + + def get_minimum_recorded(self): + return self._minimum_value + + def get_maximum_recorded(self): + return self._maximum_value + + def get_high_threshold(self): + return self._high_threshold + + def get_low_threshold(self): + return self._low_threshold + + def get_high_critical_threshold(self): + return self._high_critical_threshold + + def get_low_critical_threshold(self): + return self._low_critical_threshold + + def make_over_threshold(self): + self._high_threshold = 2 + self._value = 3 + self._low_threshold = 1 + + def make_under_threshold(self): + self._high_threshold = 3 + self._value = 1 + self._low_threshold = 2 + + def make_normal_value(self): + self._high_threshold = 3 + self._value = 2 + self._low_threshold = 1 + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + +class MockThermal(thermal_base.ThermalBase): + def __init__(self, index=None): + super(MockThermal, self).__init__() + self._name = 'Thermal {}'.format(index) if index != None else None + self._presence = True + self._model = 'Thermal Model' + self._serial = 'Thermal Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = False + + self._temperature = 2 + self._minimum_temperature = 1 + self._maximum_temperature = 5 + self._high_threshold = 3 + self._low_threshold = 1 + self._high_critical_threshold = 4 + self._low_critical_threshold = 0 + + def get_temperature(self): + return self._temperature + + def get_minimum_recorded(self): + return self._minimum_temperature + + def get_maximum_recorded(self): + return self._maximum_temperature + + def get_high_threshold(self): + return self._high_threshold + + def get_low_threshold(self): + return self._low_threshold + + def get_high_critical_threshold(self): + return self._high_critical_threshold + + def get_low_critical_threshold(self): + return self._low_critical_threshold + + def make_over_temper(self): + self._high_threshold = 2 + self._temperature = 3 + self._low_threshold = 1 + + def make_under_temper(self): + self._high_threshold = 3 + self._temperature = 1 + self._low_threshold = 2 + + def make_normal_temper(self): + self._high_threshold = 3 + self._temperature = 2 + self._low_threshold = 1 + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + +class MockErrorThermal(MockThermal): + def get_temperature(self): + raise Exception('Failed to get temperature') + +class MockErrorVsensor(MockVsensor): + def get_value(self): + raise Exception('Failed to get voltage') + +class MockErrorIsensor(MockIsensor): + def get_value(self): + raise Exception('Failed to get current') + + +class MockThermalManager(thermal_manager_base.ThermalManagerBase): + def __init__(self): + super(MockThermalManager, self).__init__() + + +class MockChassis(chassis_base.ChassisBase): + def __init__(self): + super(MockChassis, self).__init__() + self._name = None + self._presence = True + self._model = 'Chassis Model' + self._serial = 'Chassis Serial' + self._status = True + self._position_in_parent = 1 + self._replaceable = False + + self._is_chassis_system = False + self._my_slot = module_base.ModuleBase.MODULE_INVALID_SLOT + self._thermal_manager = MockThermalManager() + + def make_absent_fan(self): + fan = MockFan() + fan.set_presence(False) + fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) + fan_drawer._fan_list.append(fan) + self._fan_list.append(fan) + self._fan_drawer_list.append(fan_drawer) + + def make_faulty_fan(self): + fan = MockFan() + fan.set_status(False) + fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) + fan_drawer._fan_list.append(fan) + self._fan_list.append(fan) + self._fan_drawer_list.append(fan_drawer) + + def make_under_speed_fan(self): + fan = MockFan() + fan.make_under_speed() + fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) + fan_drawer._fan_list.append(fan) + self._fan_list.append(fan) + self._fan_drawer_list.append(fan_drawer) + + def make_over_speed_fan(self): + fan = MockFan() + fan.make_over_speed() + fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) + fan_drawer._fan_list.append(fan) + self._fan_list.append(fan) + self._fan_drawer_list.append(fan_drawer) + + def make_error_fan(self): + fan = MockErrorFan() + fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) + fan_drawer._fan_list.append(fan) + self._fan_list.append(fan) + self._fan_drawer_list.append(fan_drawer) + + def make_over_threshold_vsensor(self): + vsensor = MockVsensor() + vsensor.make_over_threshold() + self._vsensor_list.append(vsensor) + + def make_under_threshold_vsensor(self): + vsensor = MockVsensor() + vsensor.make_under_threshold() + self._vsensor_list.append(vsensor) + + def make_error_vsensor(self): + vsensor = MockErrorVsensor() + self._vsensor_list.append(vsensor) + + def make_module_vsensor(self): + module = MockModule() + self._module_list.append(module) + module._vsensor_list.append(MockVsensor()) + + def make_over_threshold_isensor(self): + isensor = MockIsensor() + isensor.make_over_threshold() + self._isensor_list.append(isensor) + + def make_under_threshold_isensor(self): + isensor = MockIsensor() + isensor.make_under_threshold() + self._isensor_list.append(isensor) + + def make_error_isensor(self): + isensor = MockErrorIsensor() + self._isensor_list.append(isensor) + + def make_module_isensor(self): + module = MockModule() + self._module_list.append(module) + module._isensor_list.append(MockIsensor()) + + def make_over_temper_thermal(self): + thermal = MockThermal() + thermal.make_over_temper() + self._thermal_list.append(thermal) + + def make_under_temper_thermal(self): + thermal = MockThermal() + thermal.make_under_temper() + self._thermal_list.append(thermal) + + def make_error_thermal(self): + thermal = MockErrorThermal() + self._thermal_list.append(thermal) + + def make_module_thermal(self): + module = MockModule() + self._module_list.append(module) + sfp = MockSfp() + sfp._thermal_list.append(MockThermal()) + psu = MockPsu() + psu._thermal_list.append(MockThermal()) + module._sfp_list.append(sfp) + module._psu_list.append(psu) + module._thermal_list.append(MockThermal()) + + def is_modular_chassis(self): + return self._is_chassis_system + + def set_modular_chassis(self, is_true): + self._is_chassis_system = is_true + + def set_my_slot(self, my_slot): + self._my_slot = my_slot + + def get_my_slot(self): + return self._my_slot + + def get_thermal_manager(self): + return self._thermal_manager + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockModule(module_base.ModuleBase): + def __init__(self): + super(MockModule, self).__init__() diff --git a/sonic-sensormond/tests/mock_swsscommon.py b/sonic-sensormond/tests/mock_swsscommon.py new file mode 100644 index 000000000..ade0d3541 --- /dev/null +++ b/sonic-sensormond/tests/mock_swsscommon.py @@ -0,0 +1,34 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +STATE_DB = '' +CHASSIS_STATE_DB = '' + + +class Table: + def __init__(self, db, table_name): + self.table_name = table_name + self.mock_dict = {} + + def _del(self, key): + del self.mock_dict[key] + pass + + def set(self, key, fvs): + self.mock_dict[key] = fvs.fv_dict + pass + + def get(self, key): + if key in self.mock_dict: + return self.mock_dict[key] + return None + + def get_size(self): + return (len(self.mock_dict)) + + +class FieldValuePairs: + def __init__(self, fvs): + self.fv_dict = dict(fvs) + pass diff --git a/sonic-sensormond/tests/mocked_libs/sonic_platform/__init__.py b/sonic-sensormond/tests/mocked_libs/sonic_platform/__init__.py new file mode 100644 index 000000000..e491d5b52 --- /dev/null +++ b/sonic-sensormond/tests/mocked_libs/sonic_platform/__init__.py @@ -0,0 +1,6 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from . import chassis +from . import platform diff --git a/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py b/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py new file mode 100644 index 000000000..49a939987 --- /dev/null +++ b/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py @@ -0,0 +1,25 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +# TODO: Clean this up once we no longer need to support Python 2 +import sys +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +from sonic_platform_base.chassis_base import ChassisBase + + +class Chassis(ChassisBase): + def __init__(self): + ChassisBase.__init__(self) + self._eeprom = mock.MagicMock() + self._thermal_manager = mock.MagicMock() + + def get_eeprom(self): + return self._eeprom + + def get_thermal_manager(self): + return self._thermal_manager diff --git a/sonic-sensormond/tests/mocked_libs/sonic_platform/platform.py b/sonic-sensormond/tests/mocked_libs/sonic_platform/platform.py new file mode 100644 index 000000000..e1e7735f3 --- /dev/null +++ b/sonic-sensormond/tests/mocked_libs/sonic_platform/platform.py @@ -0,0 +1,11 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from sonic_platform_base.platform_base import PlatformBase +from sonic_platform.chassis import Chassis + +class Platform(PlatformBase): + def __init__(self): + PlatformBase.__init__(self) + self._chassis = Chassis() diff --git a/sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py b/sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py new file mode 100644 index 000000000..012af621e --- /dev/null +++ b/sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py @@ -0,0 +1,5 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +from . import swsscommon diff --git a/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py new file mode 100644 index 000000000..13c49dec1 --- /dev/null +++ b/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py @@ -0,0 +1,56 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector + +STATE_DB = '' + + +class Table: + def __init__(self, db, table_name): + self.table_name = table_name + self.mock_dict = {} + + def _del(self, key): + del self.mock_dict[key] + pass + + def set(self, key, fvs): + self.mock_dict[key] = fvs.fv_dict + pass + + def get(self, key): + if key in self.mock_dict: + return self.mock_dict[key] + return None + + def get_size(self): + return (len(self.mock_dict)) + + +class FieldValuePairs: + fv_dict = {} + + def __init__(self, tuple_list): + if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): + self.fv_dict = dict(tuple_list) + + def __setitem__(self, key, kv_tuple): + self.fv_dict[kv_tuple[0]] = kv_tuple[1] + + def __getitem__(self, key): + return self.fv_dict[key] + + def __eq__(self, other): + if not isinstance(other, FieldValuePairs): + # don't attempt to compare against unrelated types + return NotImplemented + + return self.fv_dict == other.fv_dict + + def __repr__(self): + return repr(self.fv_dict) + + def __str__(self): + return repr(self.fv_dict) diff --git a/sonic-sensormond/tests/test_sensormond.py b/sonic-sensormond/tests/test_sensormond.py new file mode 100644 index 000000000..014bb9a7d --- /dev/null +++ b/sonic-sensormond/tests/test_sensormond.py @@ -0,0 +1,499 @@ +import os +import sys +import multiprocessing +from imp import load_source # TODO: Replace with importlib once we no longer need to support Python 2 + +# TODO: Clean this up once we no longer need to support Python 2 +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + +import pytest +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, 'mocked_libs') +sys.path.insert(0, mocked_libs_path) + + +import swsscommon +# Check we are using the mocked package +assert len(swsscommon.__path__) == 1 +assert(os.path.samefile(swsscommon.__path__[0], os.path.join(mocked_libs_path, 'swsscommon'))) + +from sonic_py_common import daemon_base + +from .mock_platform import MockChassis, MockThermal, MockVsensor, MockIsensor +from .mock_swsscommon import Table + +daemon_base.db_connect = mock.MagicMock() + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) +scripts_path = os.path.join(modules_path, 'scripts') +sys.path.insert(0, modules_path) + +load_source('sensormond', os.path.join(scripts_path, 'sensormond')) +import sensormond + + +VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' +CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' + + +@pytest.fixture(scope='function', autouse=True) +def configure_mocks(): + sensormond.SensorStatus.log_notice = mock.MagicMock() + sensormond.SensorStatus.log_warning = mock.MagicMock() + sensormond.VoltageUpdater.log_notice = mock.MagicMock() + sensormond.VoltageUpdater.log_warning = mock.MagicMock() + sensormond.CurrentUpdater.log_notice = mock.MagicMock() + sensormond.CurrentUpdater.log_warning = mock.MagicMock() + + yield + + sensormond.SensorStatus.log_notice.reset() + sensormond.SensorStatus.log_warning.reset() + sensormond.VoltageUpdater.log_notice.reset() + sensormond.VoltageUpdater.log_warning.reset() + sensormond.CurrentUpdater.log_notice.reset() + sensormond.CurrentUpdater.log_warning.reset() + +def test_sensor_status_set_over_threshold(): + sensor_status = sensormond.SensorStatus() + ret = sensor_status.set_over_threshold(sensormond.NOT_AVAILABLE, sensormond.NOT_AVAILABLE) + assert not ret + + ret = sensor_status.set_over_threshold(sensormond.NOT_AVAILABLE, 0) + assert not ret + + ret = sensor_status.set_over_threshold(0, sensormond.NOT_AVAILABLE) + assert not ret + + ret = sensor_status.set_over_threshold(2, 1) + assert ret + assert sensor_status.over_threshold + + ret = sensor_status.set_over_threshold(1, 2) + assert ret + assert not sensor_status.over_threshold + + +def test_sensor_status_set_under_threshold(): + sensor_status = sensormond.SensorStatus() + ret = sensor_status.set_under_threshold(sensormond.NOT_AVAILABLE, sensormond.NOT_AVAILABLE) + assert not ret + + ret = sensor_status.set_under_threshold(sensormond.NOT_AVAILABLE, 0) + assert not ret + + ret = sensor_status.set_under_threshold(0, sensormond.NOT_AVAILABLE) + assert not ret + + ret = sensor_status.set_under_threshold(1, 2) + assert ret + assert sensor_status.under_threshold + + ret = sensor_status.set_under_threshold(2, 1) + assert ret + assert not sensor_status.under_threshold + + +def test_sensor_status_set_not_available(): + SENSOR_NAME = 'Chassis 1 Sensor 1' + sensor_status = sensormond.SensorStatus() + sensor_status.value = 20.0 + + sensor_status.set_value(SENSOR_NAME, sensormond.NOT_AVAILABLE) + assert sensor_status.value is None + assert sensor_status.log_warning.call_count == 1 + sensor_status.log_warning.assert_called_with('Value of {} became unavailable'.format(SENSOR_NAME)) + +class TestVoltageUpdater(object): + """ + Test cases to cover functionality in VoltageUpdater class + """ + def test_deinit(self): + chassis = MockChassis() + voltage_updater = sensormond.VoltageUpdater(chassis) + voltage_updater.voltage_status_dict = {'key1': 'value1', 'key2': 'value2'} + voltage_updater.table = Table("STATE_DB", "xtable") + voltage_updater.table._del = mock.MagicMock() + voltage_updater.table.getKeys = mock.MagicMock(return_value=['key1','key2']) + voltage_updater.phy_entity_table = Table("STATE_DB", "ytable") + voltage_updater.phy_entity_table._del = mock.MagicMock() + voltage_updater.phy_entity_table.getKeys = mock.MagicMock(return_value=['key1','key2']) + voltage_updater.chassis_table = Table("STATE_DB", "ctable") + voltage_updater.chassis_table._del = mock.MagicMock() + voltage_updater.is_chassis_system = True + + voltage_updater.__del__() + assert voltage_updater.table.getKeys.call_count == 1 + assert voltage_updater.table._del.call_count == 2 + expected_calls = [mock.call('key1'), mock.call('key2')] + voltage_updater.table._del.assert_has_calls(expected_calls, any_order=True) + + def test_over_voltage(self): + chassis = MockChassis() + chassis.make_over_threshold_vsensor() + voltage_updater = sensormond.VoltageUpdater(chassis) + voltage_updater.update() + vsensor_list = chassis.get_all_vsensors() + assert voltage_updater.log_warning.call_count == 1 + voltage_updater.log_warning.assert_called_with('High voltage warning: chassis 1 vsensor 1 current voltage 3mV, high threshold 2mV') + + vsensor_list[0].make_normal_value() + voltage_updater.update() + assert voltage_updater.log_notice.call_count == 1 + voltage_updater.log_notice.assert_called_with('High voltage warning cleared: chassis 1 vsensor 1 voltage restored to 2mV, high threshold 3mV') + + def test_under_voltage(self): + chassis = MockChassis() + chassis.make_under_threshold_vsensor() + voltage_updater = sensormond.VoltageUpdater(chassis) + voltage_updater.update() + vsensor_list = chassis.get_all_vsensors() + assert voltage_updater.log_warning.call_count == 1 + voltage_updater.log_warning.assert_called_with('Low voltage warning: chassis 1 vsensor 1 current voltage 1mV, low threshold 2mV') + + vsensor_list[0].make_normal_value() + voltage_updater.update() + assert voltage_updater.log_notice.call_count == 1 + voltage_updater.log_notice.assert_called_with('Low voltage warning cleared: chassis 1 vsensor 1 voltage restored to 2mV, low threshold 1mV') + + def test_update_vsensor_with_exception(self): + chassis = MockChassis() + chassis.make_error_vsensor() + vsensor = MockVsensor() + vsensor.make_over_threshold() + chassis.get_all_vsensors().append(vsensor) + + voltage_updater = sensormond.VoltageUpdater(chassis) + voltage_updater.update() + assert voltage_updater.log_warning.call_count == 2 + + if sys.version_info.major == 3: + expected_calls = [ + mock.call("Failed to update vsensor status for chassis 1 vsensor 1 - Exception('Failed to get voltage')"), + mock.call('High voltage warning: chassis 1 vsensor 2 current voltage 3mV, high threshold 2mV') + ] + else: + expected_calls = [ + mock.call("Failed to update vsensor status for chassis 1 vsensor 1 - Exception('Failed to get voltage',)"), + mock.call('High voltage warning: chassis 1 vsensor 2 current voltage 3mV, high threshold 2mV') + ] + assert voltage_updater.log_warning.mock_calls == expected_calls + + def test_update_module_vsensors(self): + chassis = MockChassis() + chassis.make_module_vsensor() + chassis.set_modular_chassis(True) + voltage_updater = sensormond.VoltageUpdater(chassis) + voltage_updater.update() + assert len(voltage_updater.module_vsensors) == 1 + + chassis._module_list = [] + voltage_updater.update() + assert len(voltage_updater.module_vsensors) == 0 + + +class TestCurrentUpdater(object): + """ + Test cases to cover functionality in CurrentUpdater class + """ + def test_deinit(self): + chassis = MockChassis() + current_updater = sensormond.CurrentUpdater(chassis) + current_updater.current_status_dict = {'key1': 'value1', 'key2': 'value2'} + current_updater.table = Table("STATE_DB", "xtable") + current_updater.table._del = mock.MagicMock() + current_updater.table.getKeys = mock.MagicMock(return_value=['key1','key2']) + current_updater.phy_entity_table = Table("STATE_DB", "ytable") + current_updater.phy_entity_table._del = mock.MagicMock() + current_updater.phy_entity_table.getKeys = mock.MagicMock(return_value=['key1','key2']) + current_updater.chassis_table = Table("STATE_DB", "ctable") + current_updater.chassis_table._del = mock.MagicMock() + current_updater.is_chassis_system = True + + current_updater.__del__() + assert current_updater.table.getKeys.call_count == 1 + assert current_updater.table._del.call_count == 2 + expected_calls = [mock.call('key1'), mock.call('key2')] + current_updater.table._del.assert_has_calls(expected_calls, any_order=True) + + def test_over_current(self): + chassis = MockChassis() + chassis.make_over_threshold_isensor() + current_updater = sensormond.CurrentUpdater(chassis) + current_updater.update() + isensor_list = chassis.get_all_isensors() + assert current_updater.log_warning.call_count == 1 + current_updater.log_warning.assert_called_with('High Current warning: chassis 1 isensor 1 current Current 3mA, high threshold 2mA') + + isensor_list[0].make_normal_value() + current_updater.update() + assert current_updater.log_notice.call_count == 1 + current_updater.log_notice.assert_called_with('High Current warning cleared: chassis 1 isensor 1 current restored to 2mA, high threshold 3mA') + + def test_under_current(self): + chassis = MockChassis() + chassis.make_under_threshold_isensor() + current_updater = sensormond.CurrentUpdater(chassis) + current_updater.update() + isensor_list = chassis.get_all_isensors() + assert current_updater.log_warning.call_count == 1 + current_updater.log_warning.assert_called_with('Low current warning: chassis 1 isensor 1 current current 1mA, low threshold 2mA') + + isensor_list[0].make_normal_value() + current_updater.update() + assert current_updater.log_notice.call_count == 1 + current_updater.log_notice.assert_called_with('Low current warning cleared: chassis 1 isensor 1 current restored to 2mA, low threshold 1mA') + + def test_update_isensor_with_exception(self): + chassis = MockChassis() + chassis.make_error_isensor() + isensor = MockIsensor() + isensor.make_over_threshold() + chassis.get_all_isensors().append(isensor) + + current_updater = sensormond.CurrentUpdater(chassis) + current_updater.update() + assert current_updater.log_warning.call_count == 2 + + if sys.version_info.major == 3: + expected_calls = [ + mock.call("Failed to update isensor status for chassis 1 isensor 1 - Exception('Failed to get current')"), + mock.call('High Current warning: chassis 1 isensor 2 current Current 3mA, high threshold 2mA') + ] + else: + expected_calls = [ + mock.call("Failed to update isensor status for chassis 1 isensor 1 - Exception('Failed to get current',)"), + mock.call('High Current warning: chassis 1 isensor 2 current Current 3mA, high threshold 2mA') + ] + assert current_updater.log_warning.mock_calls == expected_calls + + def test_update_module_isensors(self): + chassis = MockChassis() + chassis.make_module_isensor() + chassis.set_modular_chassis(True) + current_updater = sensormond.CurrentUpdater(chassis) + current_updater.update() + assert len(current_updater.module_isensors) == 1 + + chassis._module_list = [] + current_updater.update() + assert len(current_updater.module_isensors) == 0 + +# Modular chassis-related tests + + +def test_updater_vsensor_check_modular_chassis(): + chassis = MockChassis() + assert chassis.is_modular_chassis() == False + + voltage_updater = sensormond.VoltageUpdater(chassis) + assert voltage_updater.chassis_table == None + + chassis.set_modular_chassis(True) + chassis.set_my_slot(-1) + voltage_updater = sensormond.VoltageUpdater(chassis) + assert voltage_updater.chassis_table == None + + my_slot = 1 + chassis.set_my_slot(my_slot) + voltage_updater = sensormond.VoltageUpdater(chassis) + assert voltage_updater.chassis_table != None + assert voltage_updater.chassis_table.table_name == '{}_{}'.format(VOLTAGE_INFO_TABLE_NAME, str(my_slot)) + + +def test_updater_vsensor_check_chassis_table(): + chassis = MockChassis() + + vsensor1 = MockVsensor() + chassis.get_all_vsensors().append(vsensor1) + + chassis.set_modular_chassis(True) + chassis.set_my_slot(1) + voltage_updater = sensormond.VoltageUpdater(chassis) + + voltage_updater.update() + assert voltage_updater.chassis_table.get_size() == chassis.get_num_vsensors() + + vsensor2 = MockVsensor() + chassis.get_all_vsensors().append(vsensor2) + voltage_updater.update() + assert voltage_updater.chassis_table.get_size() == chassis.get_num_vsensors() + +def test_updater_vsensor_check_min_max(): + chassis = MockChassis() + + vsensor = MockVsensor(1) + chassis.get_all_vsensors().append(vsensor) + + chassis.set_modular_chassis(True) + chassis.set_my_slot(1) + voltage_updater = sensormond.VoltageUpdater(chassis) + + voltage_updater.update() + slot_dict = voltage_updater.chassis_table.get(vsensor.get_name()) + assert slot_dict['minimum_voltage'] == str(vsensor.get_minimum_recorded()) + assert slot_dict['maximum_voltage'] == str(vsensor.get_maximum_recorded()) + + +def test_updater_isensor_check_modular_chassis(): + chassis = MockChassis() + assert chassis.is_modular_chassis() == False + + current_updater = sensormond.CurrentUpdater(chassis) + assert current_updater.chassis_table == None + + chassis.set_modular_chassis(True) + chassis.set_my_slot(-1) + current_updater = sensormond.CurrentUpdater(chassis) + assert current_updater.chassis_table == None + + my_slot = 1 + chassis.set_my_slot(my_slot) + current_updater = sensormond.CurrentUpdater(chassis) + assert current_updater.chassis_table != None + assert current_updater.chassis_table.table_name == '{}_{}'.format(CURRENT_INFO_TABLE_NAME, str(my_slot)) + + +def test_updater_isensor_check_chassis_table(): + chassis = MockChassis() + + isensor1 = MockIsensor() + chassis.get_all_isensors().append(isensor1) + + chassis.set_modular_chassis(True) + chassis.set_my_slot(1) + current_updater = sensormond.CurrentUpdater(chassis) + + current_updater.update() + assert current_updater.chassis_table.get_size() == chassis.get_num_isensors() + + isensor2 = MockIsensor() + chassis.get_all_isensors().append(isensor2) + current_updater.update() + assert current_updater.chassis_table.get_size() == chassis.get_num_isensors() + + +def test_updater_isensor_check_min_max(): + chassis = MockChassis() + + isensor = MockIsensor(1) + chassis.get_all_isensors().append(isensor) + + chassis.set_modular_chassis(True) + chassis.set_my_slot(1) + current_updater = sensormond.CurrentUpdater(chassis) + + current_updater.update() + slot_dict = current_updater.chassis_table.get(isensor.get_name()) + assert slot_dict['minimum_current'] == str(isensor.get_minimum_recorded()) + assert slot_dict['maximum_current'] == str(isensor.get_maximum_recorded()) + +def test_signal_handler(): + # Test SIGHUP + daemon_sensormond = sensormond.SensorMonitorDaemon() + daemon_sensormond.stop_event.set = mock.MagicMock() + daemon_sensormond.log_info = mock.MagicMock() + daemon_sensormond.log_warning = mock.MagicMock() + daemon_sensormond.signal_handler(sensormond.signal.SIGHUP, None) + assert daemon_sensormond.log_info.call_count == 1 + daemon_sensormond.log_info.assert_called_with("Caught signal 'SIGHUP' - ignoring...") + assert daemon_sensormond.log_warning.call_count == 0 + assert daemon_sensormond.stop_event.set.call_count == 0 + assert sensormond.exit_code == 1 + + # Test SIGINT + daemon_sensormond = sensormond.SensorMonitorDaemon() + daemon_sensormond.stop_event.set = mock.MagicMock() + daemon_sensormond.log_info = mock.MagicMock() + daemon_sensormond.log_warning = mock.MagicMock() + test_signal = sensormond.signal.SIGINT + daemon_sensormond.signal_handler(test_signal, None) + assert daemon_sensormond.log_info.call_count == 1 + daemon_sensormond.log_info.assert_called_with("Caught signal 'SIGINT' - exiting...") + assert daemon_sensormond.log_warning.call_count == 0 + assert daemon_sensormond.stop_event.set.call_count == 1 + assert sensormond.exit_code == (128 + test_signal) + + # Test SIGTERM + sensormond.exit_code = 1 + daemon_sensormond = sensormond.SensorMonitorDaemon() + daemon_sensormond.stop_event.set = mock.MagicMock() + daemon_sensormond.log_info = mock.MagicMock() + daemon_sensormond.log_warning = mock.MagicMock() + test_signal = sensormond.signal.SIGTERM + daemon_sensormond.signal_handler(test_signal, None) + assert daemon_sensormond.log_info.call_count == 1 + daemon_sensormond.log_info.assert_called_with("Caught signal 'SIGTERM' - exiting...") + assert daemon_sensormond.log_warning.call_count == 0 + assert daemon_sensormond.stop_event.set.call_count == 1 + assert sensormond.exit_code == (128 + test_signal) + + # Test an unhandled signal + sensormond.exit_code = 1 + daemon_sensormond = sensormond.SensorMonitorDaemon() + daemon_sensormond.stop_event.set = mock.MagicMock() + daemon_sensormond.log_info = mock.MagicMock() + daemon_sensormond.log_warning = mock.MagicMock() + daemon_sensormond.signal_handler(sensormond.signal.SIGUSR1, None) + assert daemon_sensormond.log_warning.call_count == 1 + daemon_sensormond.log_warning.assert_called_with("Caught unhandled signal 'SIGUSR1' - ignoring...") + assert daemon_sensormond.log_info.call_count == 0 + assert daemon_sensormond.stop_event.set.call_count == 0 + assert sensormond.exit_code == 1 + + +def test_daemon_run(): + daemon_sensormond = sensormond.SensorMonitorDaemon() + daemon_sensormond.stop_event.wait = mock.MagicMock(return_value=True) + ret = daemon_sensormond.run() + assert ret is False + + daemon_sensormond = sensormond.SensorMonitorDaemon() + daemon_sensormond.stop_event.wait = mock.MagicMock(return_value=False) + ret = daemon_sensormond.run() + assert ret is True + + +def test_try_get(): + def good_callback(): + return 'good result' + + def unimplemented_callback(): + raise NotImplementedError + + ret = sensormond.try_get(good_callback) + assert ret == 'good result' + + ret = sensormond.try_get(unimplemented_callback) + assert ret == sensormond.NOT_AVAILABLE + + ret = sensormond.try_get(unimplemented_callback, 'my default') + assert ret == 'my default' + + +def test_update_entity_info(): + mock_table = mock.MagicMock() + mock_vsensor = MockVsensor() + expected_fvp = sensormond.swsscommon.FieldValuePairs( + [('position_in_parent', '1'), + ('parent_name', 'Parent Name') + ]) + + sensormond.update_entity_info(mock_table, 'Parent Name', 'Key Name', mock_vsensor, 1) + assert mock_table.set.call_count == 1 + mock_table.set.assert_called_with('Key Name', expected_fvp) + + +@mock.patch('sensormond.SensorMonitorDaemon.run') +def test_main(mock_run): + mock_run.return_value = False + + ret = sensormond.main() + assert mock_run.call_count == 1 + assert ret != 0 From 7a4fbea4030937ee60ebcf14be4d4e4c950a09ef Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Wed, 2 Aug 2023 13:32:03 -0700 Subject: [PATCH 06/10] UT cleanup --- sonic-sensormond/tests/mock_platform.py | 387 ------------------ .../mocked_libs/sonic_platform/chassis.py | 3 - sonic-sensormond/tests/test_sensormond.py | 8 +- 3 files changed, 4 insertions(+), 394 deletions(-) diff --git a/sonic-sensormond/tests/mock_platform.py b/sonic-sensormond/tests/mock_platform.py index a09fd207b..95a6f8057 100644 --- a/sonic-sensormond/tests/mock_platform.py +++ b/sonic-sensormond/tests/mock_platform.py @@ -1,232 +1,6 @@ from sonic_platform_base import chassis_base -from sonic_platform_base import fan_base -from sonic_platform_base import fan_drawer_base from sonic_platform_base import module_base -from sonic_platform_base import psu_base -from sonic_platform_base import sfp_base -from sonic_platform_base import thermal_base from sonic_platform_base import sensor_base -from sonic_platform_base.sonic_thermal_control import thermal_manager_base - - -class MockFan(fan_base.FanBase): - def __init__(self): - super(MockFan, self).__init__() - self._name = None - self._presence = True - self._model = 'Fan Model' - self._serial = 'Fan Serial' - self._status = True - self._position_in_parent = 1 - self._replaceable = True - - self._speed = 20 - self._speed_tolerance = 20 - self._target_speed = 20 - self._direction = self.FAN_DIRECTION_INTAKE - self._status_led = self.STATUS_LED_COLOR_RED - - def get_speed(self): - return self._speed - - def get_speed_tolerance(self): - return self._speed_tolerance - - def get_target_speed(self): - return self._target_speed - - def get_direction(self): - return self._direction - - def get_status_led(self): - return self._status_led - - def set_status_led(self, value): - self._status_led = value - - def make_under_speed(self): - self._speed = 1 - self._target_speed = 2 - self._speed_tolerance = 0 - - def make_over_speed(self): - self._speed = 2 - self._target_speed = 1 - self._speed_tolerance = 0 - - def make_normal_speed(self): - self._speed = 1 - self._target_speed = 1 - self._speed_tolerance = 0 - - # Methods inherited from DeviceBase class and related setters - def get_name(self): - return self._name - - def get_presence(self): - return self._presence - - def set_presence(self, presence): - self._presence = presence - - def get_model(self): - return self._model - - def get_serial(self): - return self._serial - - def get_status(self): - return self._status - - def set_status(self, status): - self._status = status - - def get_position_in_parent(self): - return self._position_in_parent - - def is_replaceable(self): - return self._replaceable - - -class MockErrorFan(MockFan): - def get_speed(self): - raise Exception('Failed to get speed') - - -class MockFanDrawer(fan_drawer_base.FanDrawerBase): - def __init__(self, index): - super(MockFanDrawer, self).__init__() - self._name = 'FanDrawer {}'.format(index) - self._presence = True - self._model = 'Fan Drawer Model' - self._serial = 'Fan Drawer Serial' - self._status = True - self._position_in_parent = 1 - self._replaceable = True - - self._status_led = self.STATUS_LED_COLOR_RED - - def get_all_fans(self): - return self._fan_list - - def get_status_led(self): - return self._status_led - - def set_status_led(self, value): - self._status_led = value - - # Methods inherited from DeviceBase class and related setters - def get_name(self): - return self._name - - def get_presence(self): - return self._presence - - def set_presence(self, presence): - self._presence = presence - - def get_model(self): - return self._model - - def get_serial(self): - return self._serial - - def get_status(self): - return self._status - - def set_status(self, status): - self._status = status - - def get_position_in_parent(self): - return self._position_in_parent - - def is_replaceable(self): - return self._replaceable - - -class MockPsu(psu_base.PsuBase): - def __init__(self): - super(MockPsu, self).__init__() - self._name = None - self._presence = True - self._model = 'PSU Model' - self._serial = 'PSU Serial' - self._status = True - self._position_in_parent = 1 - self._replaceable = True - - def get_all_fans(self): - return self._fan_list - - # Methods inherited from DeviceBase class and related setters - def get_name(self): - return self._name - - def get_presence(self): - return self._presence - - def set_presence(self, presence): - self._presence = presence - - def get_model(self): - return self._model - - def get_serial(self): - return self._serial - - def get_status(self): - return self._status - - def set_status(self, status): - self._status = status - - def get_position_in_parent(self): - return self._position_in_parent - - def is_replaceable(self): - return self._replaceable - - -class MockSfp(sfp_base.SfpBase): - def __init__(self): - super(MockSfp, self).__init__() - self._name = None - self._presence = True - self._model = 'SFP Model' - self._serial = 'SFP Serial' - self._status = True - self._position_in_parent = 1 - self._replaceable = True - - # Methods inherited from DeviceBase class and related setters - def get_name(self): - return self._name - - def get_presence(self): - return self._presence - - def set_presence(self, presence): - self._presence = presence - - def get_model(self): - return self._model - - def get_serial(self): - return self._serial - - def get_status(self): - return self._status - - def set_status(self, status): - self._status = status - - def get_position_in_parent(self): - return self._position_in_parent - - def is_replaceable(self): - return self._replaceable - - class MockVsensor(sensor_base.VsensorBase): def __init__(self, index=None): @@ -394,93 +168,6 @@ def get_position_in_parent(self): def is_replaceable(self): return self._replaceable -class MockThermal(thermal_base.ThermalBase): - def __init__(self, index=None): - super(MockThermal, self).__init__() - self._name = 'Thermal {}'.format(index) if index != None else None - self._presence = True - self._model = 'Thermal Model' - self._serial = 'Thermal Serial' - self._status = True - self._position_in_parent = 1 - self._replaceable = False - - self._temperature = 2 - self._minimum_temperature = 1 - self._maximum_temperature = 5 - self._high_threshold = 3 - self._low_threshold = 1 - self._high_critical_threshold = 4 - self._low_critical_threshold = 0 - - def get_temperature(self): - return self._temperature - - def get_minimum_recorded(self): - return self._minimum_temperature - - def get_maximum_recorded(self): - return self._maximum_temperature - - def get_high_threshold(self): - return self._high_threshold - - def get_low_threshold(self): - return self._low_threshold - - def get_high_critical_threshold(self): - return self._high_critical_threshold - - def get_low_critical_threshold(self): - return self._low_critical_threshold - - def make_over_temper(self): - self._high_threshold = 2 - self._temperature = 3 - self._low_threshold = 1 - - def make_under_temper(self): - self._high_threshold = 3 - self._temperature = 1 - self._low_threshold = 2 - - def make_normal_temper(self): - self._high_threshold = 3 - self._temperature = 2 - self._low_threshold = 1 - - # Methods inherited from DeviceBase class and related setters - def get_name(self): - return self._name - - def get_presence(self): - return self._presence - - def set_presence(self, presence): - self._presence = presence - - def get_model(self): - return self._model - - def get_serial(self): - return self._serial - - def get_status(self): - return self._status - - def set_status(self, status): - self._status = status - - def get_position_in_parent(self): - return self._position_in_parent - - def is_replaceable(self): - return self._replaceable - -class MockErrorThermal(MockThermal): - def get_temperature(self): - raise Exception('Failed to get temperature') - class MockErrorVsensor(MockVsensor): def get_value(self): raise Exception('Failed to get voltage') @@ -489,12 +176,6 @@ class MockErrorIsensor(MockIsensor): def get_value(self): raise Exception('Failed to get current') - -class MockThermalManager(thermal_manager_base.ThermalManagerBase): - def __init__(self): - super(MockThermalManager, self).__init__() - - class MockChassis(chassis_base.ChassisBase): def __init__(self): super(MockChassis, self).__init__() @@ -508,46 +189,6 @@ def __init__(self): self._is_chassis_system = False self._my_slot = module_base.ModuleBase.MODULE_INVALID_SLOT - self._thermal_manager = MockThermalManager() - - def make_absent_fan(self): - fan = MockFan() - fan.set_presence(False) - fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) - fan_drawer._fan_list.append(fan) - self._fan_list.append(fan) - self._fan_drawer_list.append(fan_drawer) - - def make_faulty_fan(self): - fan = MockFan() - fan.set_status(False) - fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) - fan_drawer._fan_list.append(fan) - self._fan_list.append(fan) - self._fan_drawer_list.append(fan_drawer) - - def make_under_speed_fan(self): - fan = MockFan() - fan.make_under_speed() - fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) - fan_drawer._fan_list.append(fan) - self._fan_list.append(fan) - self._fan_drawer_list.append(fan_drawer) - - def make_over_speed_fan(self): - fan = MockFan() - fan.make_over_speed() - fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) - fan_drawer._fan_list.append(fan) - self._fan_list.append(fan) - self._fan_drawer_list.append(fan_drawer) - - def make_error_fan(self): - fan = MockErrorFan() - fan_drawer = MockFanDrawer(len(self._fan_drawer_list)) - fan_drawer._fan_list.append(fan) - self._fan_list.append(fan) - self._fan_drawer_list.append(fan_drawer) def make_over_threshold_vsensor(self): vsensor = MockVsensor() @@ -587,31 +228,6 @@ def make_module_isensor(self): self._module_list.append(module) module._isensor_list.append(MockIsensor()) - def make_over_temper_thermal(self): - thermal = MockThermal() - thermal.make_over_temper() - self._thermal_list.append(thermal) - - def make_under_temper_thermal(self): - thermal = MockThermal() - thermal.make_under_temper() - self._thermal_list.append(thermal) - - def make_error_thermal(self): - thermal = MockErrorThermal() - self._thermal_list.append(thermal) - - def make_module_thermal(self): - module = MockModule() - self._module_list.append(module) - sfp = MockSfp() - sfp._thermal_list.append(MockThermal()) - psu = MockPsu() - psu._thermal_list.append(MockThermal()) - module._sfp_list.append(sfp) - module._psu_list.append(psu) - module._thermal_list.append(MockThermal()) - def is_modular_chassis(self): return self._is_chassis_system @@ -624,9 +240,6 @@ def set_my_slot(self, my_slot): def get_my_slot(self): return self._my_slot - def get_thermal_manager(self): - return self._thermal_manager - # Methods inherited from DeviceBase class and related setters def get_name(self): return self._name diff --git a/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py b/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py index 49a939987..325865e2f 100644 --- a/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py +++ b/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py @@ -20,6 +20,3 @@ def __init__(self): def get_eeprom(self): return self._eeprom - - def get_thermal_manager(self): - return self._thermal_manager diff --git a/sonic-sensormond/tests/test_sensormond.py b/sonic-sensormond/tests/test_sensormond.py index 014bb9a7d..84227b8d5 100644 --- a/sonic-sensormond/tests/test_sensormond.py +++ b/sonic-sensormond/tests/test_sensormond.py @@ -1,9 +1,9 @@ import os import sys import multiprocessing -from imp import load_source # TODO: Replace with importlib once we no longer need to support Python 2 -# TODO: Clean this up once we no longer need to support Python 2 +# Python 2 support +from imp import load_source if sys.version_info.major == 3: from unittest import mock else: @@ -16,15 +16,15 @@ mocked_libs_path = os.path.join(tests_path, 'mocked_libs') sys.path.insert(0, mocked_libs_path) - import swsscommon + # Check we are using the mocked package assert len(swsscommon.__path__) == 1 assert(os.path.samefile(swsscommon.__path__[0], os.path.join(mocked_libs_path, 'swsscommon'))) from sonic_py_common import daemon_base -from .mock_platform import MockChassis, MockThermal, MockVsensor, MockIsensor +from .mock_platform import MockChassis, MockVsensor, MockIsensor from .mock_swsscommon import Table daemon_base.db_connect = mock.MagicMock() From 51a5887bab3161dd62526694829d323ec7a6bf37 Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Tue, 8 Aug 2023 22:31:15 -0700 Subject: [PATCH 07/10] Addressed comments. --- sonic-sensormond/scripts/sensormond | 219 ++++++++---------- sonic-sensormond/setup.py | 1 - sonic-sensormond/tests/mock_platform.py | 76 +++--- sonic-sensormond/tests/mock_swsscommon.py | 34 --- .../mocked_libs/sonic_platform/chassis.py | 8 +- .../mocked_libs/swsscommon/swsscommon.py | 4 + sonic-sensormond/tests/test_sensormond.py | 188 +++++++-------- 7 files changed, 234 insertions(+), 296 deletions(-) delete mode 100644 sonic-sensormond/tests/mock_swsscommon.py diff --git a/sonic-sensormond/scripts/sensormond b/sonic-sensormond/scripts/sensormond index ad3765485..a8a2a53c0 100755 --- a/sonic-sensormond/scripts/sensormond +++ b/sonic-sensormond/scripts/sensormond @@ -1,19 +1,17 @@ #!/usr/bin/python3 -""" +''' sensormond Sensor monitor daemon for SONiC -""" +''' import signal import sys import threading import time -from datetime import datetime import sonic_platform from sonic_py_common import daemon_base, logger -from sonic_py_common.task_base import ProcessTaskBase from swsscommon import swsscommon @@ -24,22 +22,19 @@ INVALID_SLOT = -1 PHYSICAL_ENTITY_INFO_TABLE = 'PHYSICAL_ENTITY_INFO' -# Python2 support. -SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) - for n in dir(signal) if n.startswith('SIG') and '_' not in n) - -# Exit with non-zero exit code so supervisord will restart Sensormon. -exit_code = 1 +# Exit with non-zero exit code by default so supervisord will restart Sensormon. +SENSORMON_ERROR_EXIT = 1 +exit_code = SENSORMON_ERROR_EXIT # Utility functions def try_get(callback, default=NOT_AVAILABLE): - """ + ''' Handy function to invoke the callback and catch NotImplementedError :param callback: Callback to be invoked :param default: Default return value if exception occur :return: Default return value if exception occur else return value of the callback - """ + ''' try: ret = callback() if ret is None: @@ -65,11 +60,11 @@ class SensorStatus(logger.Logger): self.under_threshold = False def set_value(self, name, value): - """ + ''' Record sensor changes. :param name: Name of the sensor. :param value: New value. - """ + ''' if value == NOT_AVAILABLE: if self.value is not None: self.log_warning('Value of {} became unavailable'.format(name)) @@ -79,12 +74,12 @@ class SensorStatus(logger.Logger): self.value = value def set_over_threshold(self, value, threshold): - """ + ''' Set over threshold status :param value: value :param threshold: High threshold :return: True if over threshold status changed else False - """ + ''' if value == NOT_AVAILABLE or threshold == NOT_AVAILABLE: old_status = self.over_threshold self.over_threshold = False @@ -98,12 +93,12 @@ class SensorStatus(logger.Logger): return True def set_under_threshold(self, value, threshold): - """ + ''' Set under value status :param value: value :param threshold: Low threshold :return: True if under threshold status changed else False - """ + ''' if value == NOT_AVAILABLE or threshold == NOT_AVAILABLE: old_status = self.under_threshold self.under_threshold = False @@ -125,22 +120,22 @@ class VoltageUpdater(logger.Logger): VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' def __init__(self, chassis): - """ + ''' Initializer of VoltageUpdater :param chassis: Object representing a platform chassis - """ + ''' super(VoltageUpdater, self).__init__(SYSLOG_IDENTIFIER) self.chassis = chassis self.voltage_status_dict = {} state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, VoltageUpdater.VOLTAGE_INFO_TABLE_NAME) + self.table = swsscommon.Table(state_db, self.VOLTAGE_INFO_TABLE_NAME) self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) self.chassis_table = None self.is_chassis_system = chassis.is_modular_chassis() if self.is_chassis_system: - self.module_vsensors = set() + self.module_voltage_sensors = set() my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) if my_slot != INVALID_SLOT: try: @@ -164,58 +159,56 @@ class VoltageUpdater(logger.Logger): self.phy_entity_table._del(pek) def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): - """ + ''' Log when any status changed :param normal_status: Expected status. :param normal_log: Log string for expected status. :param abnormal_log: Log string for unexpected status :return: - """ + ''' if normal_status: self.log_notice(normal_log) else: self.log_warning(abnormal_log) def update(self): - """ + ''' Update all voltage information to database :return: - """ + ''' self.log_debug("Start voltage update") - for index, voltage_sensor in enumerate(self.chassis.get_all_vsensors()): + for index, voltage_sensor in enumerate(self.chassis.get_all_voltage_sensors()): - print(index, voltage_sensor) self._refresh_voltage_status(CHASSIS_INFO_KEY, voltage_sensor, index) if self.is_chassis_system: - available_vsensors = set() + available_voltage_sensors = set() for module_index, module in enumerate(self.chassis.get_all_modules()): module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) - for vsensor_index, vsensor in enumerate(module.get_all_vsensors()): - available_vsensors.add((vsensor, module_name, vsensor_index)) - self._refresh_voltage_status(module_name, vsensor, vsensor_index) - - vsensors_to_remove = self.module_vsensors - available_vsensors - self.module_vsensors = available_vsensors - for vsensor, parent_name, vsensor_index in vsensors_to_remove: - self._remove_vsensor_from_db(vsensor, parent_name, vsensor_index) + for voltage_sensor_index, voltage_sensor in enumerate(module.get_all_voltage_sensors()): + available_voltage_sensors.add((voltage_sensor, module_name, voltage_sensor_index)) + self._refresh_voltage_status(module_name, voltage_sensor, voltage_sensor_index) + + voltage_sensors_to_remove = self.module_voltage_sensors - available_voltage_sensors + self.module_voltage_sensors = available_voltage_sensors + for voltage_sensor, parent_name, voltage_sensor_index in voltage_sensors_to_remove: + self._remove_voltage_sensor_from_db(voltage_sensor, parent_name, voltage_sensor_index) self.log_debug("End Voltage updating") - def _refresh_voltage_status(self, parent_name, vsensor, vsensor_index): - """ + def _refresh_voltage_status(self, parent_name, voltage_sensor, voltage_sensor_index): + ''' Get voltage status by platform API and write to database - :param parent_name: Name of parent device of the vsensor object - :param vsensor: Object representing a platform voltage vsensor - :param vsensor_index: Index of the vsensor object in platform chassis + :param parent_name: Name of parent device of the voltage_sensor object + :param voltage_sensor: Object representing a platform voltage voltage_sensor + :param voltage_sensor_index: Index of the voltage_sensor object in platform chassis :return: - """ + ''' try: - name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) - print(name) + name = try_get(voltage_sensor.get_name, '{} voltage_sensor {}'.format(parent_name, voltage_sensor_index + 1)) - update_entity_info(self.phy_entity_table, parent_name, name, vsensor, vsensor_index + 1) + update_entity_info(self.phy_entity_table, parent_name, name, voltage_sensor, voltage_sensor_index + 1) if name not in self.voltage_status_dict: self.voltage_status_dict[name] = SensorStatus() @@ -228,17 +221,16 @@ class VoltageUpdater(logger.Logger): low_critical_threshold = NOT_AVAILABLE maximum_voltage = NOT_AVAILABLE minimum_voltage = NOT_AVAILABLE - voltage = try_get(vsensor.get_value) - print("Voltage Read: ", voltage) - is_replaceable = try_get(vsensor.is_replaceable, False) + voltage = try_get(voltage_sensor.get_value) + is_replaceable = try_get(voltage_sensor.is_replaceable, False) if voltage != NOT_AVAILABLE: voltage_status.set_value(name, voltage) - minimum_voltage = try_get(vsensor.get_minimum_recorded) - maximum_voltage = try_get(vsensor.get_maximum_recorded) - high_threshold = try_get(vsensor.get_high_threshold) - low_threshold = try_get(vsensor.get_low_threshold) - high_critical_threshold = try_get(vsensor.get_high_critical_threshold) - low_critical_threshold = try_get(vsensor.get_low_critical_threshold) + minimum_voltage = try_get(voltage_sensor.get_minimum_recorded) + maximum_voltage = try_get(voltage_sensor.get_maximum_recorded) + high_threshold = try_get(voltage_sensor.get_high_threshold) + low_threshold = try_get(voltage_sensor.get_low_threshold) + high_critical_threshold = try_get(voltage_sensor.get_high_critical_threshold) + low_critical_threshold = try_get(voltage_sensor.get_low_critical_threshold) warning = False if voltage != NOT_AVAILABLE and voltage_status.set_over_threshold(voltage, high_threshold): @@ -269,17 +261,17 @@ class VoltageUpdater(logger.Logger): ('critical_high_threshold', str(high_critical_threshold)), ('critical_low_threshold', str(low_critical_threshold)), ('is_replaceable', str(is_replaceable)), - ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) + ('timestamp', time.strftime('%Y%m%d %H:%M:%S')) ]) self.table.set(name, fvs) if self.is_chassis_system and self.chassis_table is not None: self.chassis_table.set(name, fvs) except Exception as e: - self.log_warning('Failed to update vsensor status for {} - {}'.format(name, repr(e))) + self.log_warning('Failed to update voltage_sensor status for {} - {}'.format(name, repr(e))) - def _remove_vsensor_from_db(self, vsensor, parent_name, vsensor_index): - name = try_get(vsensor.get_name, '{} vsensor {}'.format(parent_name, vsensor_index + 1)) + def _remove_voltage_sensor_from_db(self, voltage_sensor, parent_name, voltage_sensor_index): + name = try_get(voltage_sensor.get_name, '{} voltage_sensor {}'.format(parent_name, voltage_sensor_index + 1)) self.table._del(name) if self.chassis_table is not None: @@ -293,22 +285,22 @@ class CurrentUpdater(logger.Logger): CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' def __init__(self, chassis): - """ + ''' Initializer of CurrentUpdater :param chassis: Object representing a platform chassis - """ + ''' super(CurrentUpdater, self).__init__(SYSLOG_IDENTIFIER) self.chassis = chassis self.current_status_dict = {} state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, CurrentUpdater.CURRENT_INFO_TABLE_NAME) + self.table = swsscommon.Table(state_db, self.CURRENT_INFO_TABLE_NAME) self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) self.chassis_table = None self.is_chassis_system = chassis.is_modular_chassis() if self.is_chassis_system: - self.module_isensors = set() + self.module_current_sensors = set() my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) if my_slot != INVALID_SLOT: try: @@ -332,62 +324,56 @@ class CurrentUpdater(logger.Logger): self.phy_entity_table._del(pek) def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): - """ + ''' Log when any status changed :param normal_status: Expected status. :param normal_log: Log string for expected status. :param abnormal_log: Log string for unexpected status :return: - """ + ''' if normal_status: self.log_notice(normal_log) - print(normal_log) else: self.log_warning(abnormal_log) - print(abnormal_log) - print(vars(self)) - print(sys.path) def update(self): - """ + ''' Update all current information to database :return: - """ + ''' self.log_debug("Start current updating") - for index, current_sensor in enumerate(self.chassis.get_all_isensors()): + for index, current_sensor in enumerate(self.chassis.get_all_current_sensors()): - print(index, current_sensor) self._refresh_current_status(CHASSIS_INFO_KEY, current_sensor, index) if self.is_chassis_system: - available_isensors = set() + available_current_sensors = set() for module_index, module in enumerate(self.chassis.get_all_modules()): module_name = try_get(module.get_name, 'Module {}'.format(module_index + 1)) - for isensor_index, isensor in enumerate(module.get_all_isensors()): - available_isensors.add((isensor, module_name, isensor_index)) - self._refresh_current_status(module_name, isensor, isensor_index) - - isensors_to_remove = self.module_isensors - available_isensors - self.module_isensors = available_isensors - for isensor, parent_name, isensor_index in isensors_to_remove: - self._remove_isensor_from_db(isensor, parent_name, isensor_index) + for current_sensor_index, current_sensor in enumerate(module.get_all_current_sensors()): + available_current_sensors.add((current_sensor, module_name, current_sensor_index)) + self._refresh_current_status(module_name, current_sensor, current_sensor_index) + + current_sensors_to_remove = self.module_current_sensors - available_current_sensors + self.module_current_sensors = available_current_sensors + for current_sensor, parent_name, current_sensor_index in current_sensors_to_remove: + self._remove_current_sensor_from_db(current_sensor, parent_name, current_sensor_index) self.log_debug("End Current updating") - def _refresh_current_status(self, parent_name, isensor, isensor_index): - """ + def _refresh_current_status(self, parent_name, current_sensor, current_sensor_index): + ''' Get current status by platform API and write to database - :param parent_name: Name of parent device of the isensor object - :param isensor: Object representing a platform current isensor - :param isensor_index: Index of the isensor object in platform chassis + :param parent_name: Name of parent device of the current_sensor object + :param current_sensor: Object representing a platform current current_sensor + :param current_sensor_index: Index of the current_sensor object in platform chassis :return: - """ + ''' try: - name = try_get(isensor.get_name, '{} isensor {}'.format(parent_name, isensor_index + 1)) - print(name) + name = try_get(current_sensor.get_name, '{} current_sensor {}'.format(parent_name, current_sensor_index + 1)) - update_entity_info(self.phy_entity_table, parent_name, name, isensor, isensor_index + 1) + update_entity_info(self.phy_entity_table, parent_name, name, current_sensor, current_sensor_index + 1) if name not in self.current_status_dict: self.current_status_dict[name] = SensorStatus() @@ -400,20 +386,18 @@ class CurrentUpdater(logger.Logger): low_critical_threshold = NOT_AVAILABLE maximum_current = NOT_AVAILABLE minimum_current = NOT_AVAILABLE - current = try_get(isensor.get_value) - print("Current Read: ", current) - is_replaceable = try_get(isensor.is_replaceable, False) + current = try_get(current_sensor.get_value) + is_replaceable = try_get(current_sensor.is_replaceable, False) if current != NOT_AVAILABLE: current_status.set_value(name, current) - minimum_current = try_get(isensor.get_minimum_recorded) - maximum_current = try_get(isensor.get_maximum_recorded) - high_threshold = try_get(isensor.get_high_threshold) - low_threshold = try_get(isensor.get_low_threshold) - high_critical_threshold = try_get(isensor.get_high_critical_threshold) - low_critical_threshold = try_get(isensor.get_low_critical_threshold) + minimum_current = try_get(current_sensor.get_minimum_recorded) + maximum_current = try_get(current_sensor.get_maximum_recorded) + high_threshold = try_get(current_sensor.get_high_threshold) + low_threshold = try_get(current_sensor.get_low_threshold) + high_critical_threshold = try_get(current_sensor.get_high_critical_threshold) + low_critical_threshold = try_get(current_sensor.get_low_critical_threshold) warning = False - print(current_status) if current != NOT_AVAILABLE and current_status.set_over_threshold(current, high_threshold): self._log_on_status_changed(not current_status.over_threshold, 'High Current warning cleared: {} current restored to {}mA, high threshold {}mA'. @@ -442,17 +426,17 @@ class CurrentUpdater(logger.Logger): ('critical_high_threshold', str(high_critical_threshold)), ('critical_low_threshold', str(low_critical_threshold)), ('is_replaceable', str(is_replaceable)), - ('timestamp', datetime.now().strftime('%Y%m%d %H:%M:%S')) + ('timestamp', time.strftime('%Y%m%d %H:%M:%S')) ]) self.table.set(name, fvs) if self.is_chassis_system and self.chassis_table is not None: self.chassis_table.set(name, fvs) except Exception as e: - self.log_warning('Failed to update isensor status for {} - {}'.format(name, repr(e))) + self.log_warning('Failed to update current_sensor status for {} - {}'.format(name, repr(e))) - def _remove_isensor_from_db(self, isensor, parent_name, isensor_index): - name = try_get(isensor.get_name, '{} isensor {}'.format(parent_name, isensor_index + 1)) + def _remove_current_sensor_from_db(self, current_sensor, parent_name, current_sensor_index): + name = try_get(current_sensor.get_name, '{} current_sensor {}'.format(parent_name, current_sensor_index + 1)) self.table._del(name) if self.chassis_table is not None: @@ -471,9 +455,9 @@ class SensorMonitorDaemon(daemon_base.DaemonBase): UPDATE_ELAPSED_THRESHOLD = 30 def __init__(self): - """ + ''' Initializer of SensorMonitorDaemon - """ + ''' super(SensorMonitorDaemon, self).__init__(SYSLOG_IDENTIFIER) # Set minimum logging level to INFO @@ -485,7 +469,10 @@ class SensorMonitorDaemon(daemon_base.DaemonBase): self.interval = self.UPDATE_INTERVAL - self.chassis = sonic_platform.platform.Platform().get_chassis() + try: + self.chassis = sonic_platform.platform.Platform().get_chassis() + except Exception as e: + self.log_error("Failed to get chassis info, err: {}".format(repr(e))) self.voltage_updater = VoltageUpdater(self.chassis) @@ -494,32 +481,32 @@ class SensorMonitorDaemon(daemon_base.DaemonBase): # Override signal handler from DaemonBase def signal_handler(self, sig, frame): - """ + ''' Signal handler :param sig: Signal number :param frame: not used :return: - """ + ''' FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] NONFATAL_SIGNALS = [signal.SIGHUP] global exit_code if sig in FATAL_SIGNALS: - self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_info("Caught signal '{}' - exiting...".format(signal.Signals(sig).name)) exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us self.stop_event.set() elif sig in NONFATAL_SIGNALS: - self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_info("Caught signal '{}' - ignoring...".format(signal.Signals(sig).name)) else: - self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + self.log_warning("Caught unhandled signal '{}' - ignoring...".format(signal.Signals(sig).name)) # Main daemon logic def run(self): - """ + ''' Run main logical of this daemon :return: - """ + ''' if self.stop_event.wait(self.wait_time): # We received a fatal signal return False @@ -552,7 +539,7 @@ def main(): while sensor_control.run(): pass - sensor_control.log_info("Shutting down with exit code {}...".format(exit_code)) + sensor_control.log_info("Shutting down with exit code {}".format(exit_code)) return exit_code diff --git a/sonic-sensormond/setup.py b/sonic-sensormond/setup.py index a9dc038ff..1aa5e3c08 100644 --- a/sonic-sensormond/setup.py +++ b/sonic-sensormond/setup.py @@ -28,7 +28,6 @@ ], classifiers=[ 'Development Status :: 4 - Beta', - 'Environment :: No Input/Output (Daemon)', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: System Administrators', diff --git a/sonic-sensormond/tests/mock_platform.py b/sonic-sensormond/tests/mock_platform.py index 95a6f8057..4ea05c6ea 100644 --- a/sonic-sensormond/tests/mock_platform.py +++ b/sonic-sensormond/tests/mock_platform.py @@ -2,13 +2,13 @@ from sonic_platform_base import module_base from sonic_platform_base import sensor_base -class MockVsensor(sensor_base.VsensorBase): +class MockVoltageSensor(sensor_base.VoltageSensorBase): def __init__(self, index=None): - super(MockVsensor, self).__init__() - self._name = 'Vsensor {}'.format(index) if index != None else None + super(MockVoltageSensor, self).__init__() + self._name = 'Voltage sensor {}'.format(index) if index != None else None self._presence = True - self._model = 'Vsensor Model' - self._serial = 'Vsensor Serial' + self._model = 'Voltage sensor model' + self._serial = 'Voltage sensor serial' self._status = True self._position_in_parent = 1 self._replaceable = False @@ -85,13 +85,13 @@ def get_position_in_parent(self): def is_replaceable(self): return self._replaceable -class MockIsensor(sensor_base.IsensorBase): +class MockCurrentSensor(sensor_base.CurrentSensorBase): def __init__(self, index=None): - super(MockIsensor, self).__init__() - self._name = 'Isensor {}'.format(index) if index != None else None + super(MockCurrentSensor, self).__init__() + self._name = 'Current sensor {}'.format(index) if index != None else None self._presence = True - self._model = 'Isensor Model' - self._serial = 'Isensor Serial' + self._model = 'Current sensor model' + self._serial = 'Current sensor serial' self._status = True self._position_in_parent = 1 self._replaceable = False @@ -168,11 +168,11 @@ def get_position_in_parent(self): def is_replaceable(self): return self._replaceable -class MockErrorVsensor(MockVsensor): +class MockErrorVoltageSensor(MockVoltageSensor): def get_value(self): raise Exception('Failed to get voltage') -class MockErrorIsensor(MockIsensor): +class MockErrorCurrentSensor(MockCurrentSensor): def get_value(self): raise Exception('Failed to get current') @@ -190,43 +190,43 @@ def __init__(self): self._is_chassis_system = False self._my_slot = module_base.ModuleBase.MODULE_INVALID_SLOT - def make_over_threshold_vsensor(self): - vsensor = MockVsensor() - vsensor.make_over_threshold() - self._vsensor_list.append(vsensor) + def make_over_threshold_voltage_sensor(self): + voltage_sensor = MockVoltageSensor() + voltage_sensor.make_over_threshold() + self._voltage_sensor_list.append(voltage_sensor) - def make_under_threshold_vsensor(self): - vsensor = MockVsensor() - vsensor.make_under_threshold() - self._vsensor_list.append(vsensor) + def make_under_threshold_voltage_sensor(self): + voltage_sensor = MockVoltageSensor() + voltage_sensor.make_under_threshold() + self._voltage_sensor_list.append(voltage_sensor) - def make_error_vsensor(self): - vsensor = MockErrorVsensor() - self._vsensor_list.append(vsensor) + def make_error_voltage_sensor(self): + voltage_sensor = MockErrorVoltageSensor() + self._voltage_sensor_list.append(voltage_sensor) - def make_module_vsensor(self): + def make_module_voltage_sensor(self): module = MockModule() self._module_list.append(module) - module._vsensor_list.append(MockVsensor()) + module._voltage_sensor_list.append(MockVoltageSensor()) - def make_over_threshold_isensor(self): - isensor = MockIsensor() - isensor.make_over_threshold() - self._isensor_list.append(isensor) + def make_over_threshold_current_sensor(self): + current_sensor = MockCurrentSensor() + current_sensor.make_over_threshold() + self._current_sensor_list.append(current_sensor) - def make_under_threshold_isensor(self): - isensor = MockIsensor() - isensor.make_under_threshold() - self._isensor_list.append(isensor) + def make_under_threshold_current_sensor(self): + current_sensor = MockCurrentSensor() + current_sensor.make_under_threshold() + self._current_sensor_list.append(current_sensor) - def make_error_isensor(self): - isensor = MockErrorIsensor() - self._isensor_list.append(isensor) + def make_error_current_sensor(self): + current_sensor = MockErrorCurrentSensor() + self._current_sensor_list.append(current_sensor) - def make_module_isensor(self): + def make_module_current_sensor(self): module = MockModule() self._module_list.append(module) - module._isensor_list.append(MockIsensor()) + module._current_sensor_list.append(MockCurrentSensor()) def is_modular_chassis(self): return self._is_chassis_system diff --git a/sonic-sensormond/tests/mock_swsscommon.py b/sonic-sensormond/tests/mock_swsscommon.py deleted file mode 100644 index ade0d3541..000000000 --- a/sonic-sensormond/tests/mock_swsscommon.py +++ /dev/null @@ -1,34 +0,0 @@ -''' - Mock implementation of swsscommon package for unit testing -''' - -STATE_DB = '' -CHASSIS_STATE_DB = '' - - -class Table: - def __init__(self, db, table_name): - self.table_name = table_name - self.mock_dict = {} - - def _del(self, key): - del self.mock_dict[key] - pass - - def set(self, key, fvs): - self.mock_dict[key] = fvs.fv_dict - pass - - def get(self, key): - if key in self.mock_dict: - return self.mock_dict[key] - return None - - def get_size(self): - return (len(self.mock_dict)) - - -class FieldValuePairs: - def __init__(self, fvs): - self.fv_dict = dict(fvs) - pass diff --git a/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py b/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py index 325865e2f..ed76efbf7 100644 --- a/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py +++ b/sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py @@ -2,13 +2,8 @@ Mock implementation of sonic_platform package for unit testing """ -# TODO: Clean this up once we no longer need to support Python 2 import sys -if sys.version_info.major == 3: - from unittest import mock -else: - import mock - +from unittest import mock from sonic_platform_base.chassis_base import ChassisBase @@ -16,7 +11,6 @@ class Chassis(ChassisBase): def __init__(self): ChassisBase.__init__(self) self._eeprom = mock.MagicMock() - self._thermal_manager = mock.MagicMock() def get_eeprom(self): return self._eeprom diff --git a/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py index 13c49dec1..be9b0544c 100644 --- a/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py +++ b/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py @@ -11,6 +11,7 @@ class Table: def __init__(self, db, table_name): self.table_name = table_name self.mock_dict = {} + self.mock_keys = [] def _del(self, key): del self.mock_dict[key] @@ -28,6 +29,9 @@ def get(self, key): def get_size(self): return (len(self.mock_dict)) + def getKeys(self): + return self.mock_keys + class FieldValuePairs: fv_dict = {} diff --git a/sonic-sensormond/tests/test_sensormond.py b/sonic-sensormond/tests/test_sensormond.py index 84227b8d5..524ba54ad 100644 --- a/sonic-sensormond/tests/test_sensormond.py +++ b/sonic-sensormond/tests/test_sensormond.py @@ -1,39 +1,26 @@ import os import sys import multiprocessing - -# Python 2 support from imp import load_source -if sys.version_info.major == 3: - from unittest import mock -else: - import mock - +from unittest import mock import pytest -tests_path = os.path.dirname(os.path.abspath(__file__)) - -# Add mocked_libs path so that the file under test can load mocked modules from there -mocked_libs_path = os.path.join(tests_path, 'mocked_libs') -sys.path.insert(0, mocked_libs_path) - -import swsscommon - -# Check we are using the mocked package -assert len(swsscommon.__path__) == 1 -assert(os.path.samefile(swsscommon.__path__[0], os.path.join(mocked_libs_path, 'swsscommon'))) - from sonic_py_common import daemon_base -from .mock_platform import MockChassis, MockVsensor, MockIsensor -from .mock_swsscommon import Table +# Setup load paths for mocked modules -daemon_base.db_connect = mock.MagicMock() - -# Add path to the file under test so that we can load it +tests_path = os.path.dirname(os.path.abspath(__file__)) +mocked_libs_path = os.path.join(tests_path, 'mocked_libs') +sys.path.insert(0, mocked_libs_path) modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, 'scripts') sys.path.insert(0, modules_path) +# Import mocked modules + +from swsscommon.swsscommon import Table +from .mock_platform import MockChassis, MockVoltageSensor, MockCurrentSensor + +# Load file under test load_source('sensormond', os.path.join(scripts_path, 'sensormond')) import sensormond @@ -41,6 +28,7 @@ VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' +daemon_base.db_connect = mock.MagicMock() @pytest.fixture(scope='function', autouse=True) def configure_mocks(): @@ -136,38 +124,38 @@ def test_deinit(self): def test_over_voltage(self): chassis = MockChassis() - chassis.make_over_threshold_vsensor() + chassis.make_over_threshold_voltage_sensor() voltage_updater = sensormond.VoltageUpdater(chassis) voltage_updater.update() - vsensor_list = chassis.get_all_vsensors() + voltage_sensor_list = chassis.get_all_voltage_sensors() assert voltage_updater.log_warning.call_count == 1 - voltage_updater.log_warning.assert_called_with('High voltage warning: chassis 1 vsensor 1 current voltage 3mV, high threshold 2mV') + voltage_updater.log_warning.assert_called_with('High voltage warning: chassis 1 voltage_sensor 1 current voltage 3mV, high threshold 2mV') - vsensor_list[0].make_normal_value() + voltage_sensor_list[0].make_normal_value() voltage_updater.update() assert voltage_updater.log_notice.call_count == 1 - voltage_updater.log_notice.assert_called_with('High voltage warning cleared: chassis 1 vsensor 1 voltage restored to 2mV, high threshold 3mV') + voltage_updater.log_notice.assert_called_with('High voltage warning cleared: chassis 1 voltage_sensor 1 voltage restored to 2mV, high threshold 3mV') def test_under_voltage(self): chassis = MockChassis() - chassis.make_under_threshold_vsensor() + chassis.make_under_threshold_voltage_sensor() voltage_updater = sensormond.VoltageUpdater(chassis) voltage_updater.update() - vsensor_list = chassis.get_all_vsensors() + voltage_sensor_list = chassis.get_all_voltage_sensors() assert voltage_updater.log_warning.call_count == 1 - voltage_updater.log_warning.assert_called_with('Low voltage warning: chassis 1 vsensor 1 current voltage 1mV, low threshold 2mV') + voltage_updater.log_warning.assert_called_with('Low voltage warning: chassis 1 voltage_sensor 1 current voltage 1mV, low threshold 2mV') - vsensor_list[0].make_normal_value() + voltage_sensor_list[0].make_normal_value() voltage_updater.update() assert voltage_updater.log_notice.call_count == 1 - voltage_updater.log_notice.assert_called_with('Low voltage warning cleared: chassis 1 vsensor 1 voltage restored to 2mV, low threshold 1mV') + voltage_updater.log_notice.assert_called_with('Low voltage warning cleared: chassis 1 voltage_sensor 1 voltage restored to 2mV, low threshold 1mV') - def test_update_vsensor_with_exception(self): + def test_update_voltage_sensor_with_exception(self): chassis = MockChassis() - chassis.make_error_vsensor() - vsensor = MockVsensor() - vsensor.make_over_threshold() - chassis.get_all_vsensors().append(vsensor) + chassis.make_error_voltage_sensor() + voltage_sensor = MockVoltageSensor() + voltage_sensor.make_over_threshold() + chassis.get_all_voltage_sensors().append(voltage_sensor) voltage_updater = sensormond.VoltageUpdater(chassis) voltage_updater.update() @@ -175,27 +163,27 @@ def test_update_vsensor_with_exception(self): if sys.version_info.major == 3: expected_calls = [ - mock.call("Failed to update vsensor status for chassis 1 vsensor 1 - Exception('Failed to get voltage')"), - mock.call('High voltage warning: chassis 1 vsensor 2 current voltage 3mV, high threshold 2mV') + mock.call("Failed to update voltage_sensor status for chassis 1 voltage_sensor 1 - Exception('Failed to get voltage')"), + mock.call('High voltage warning: chassis 1 voltage_sensor 2 current voltage 3mV, high threshold 2mV') ] else: expected_calls = [ - mock.call("Failed to update vsensor status for chassis 1 vsensor 1 - Exception('Failed to get voltage',)"), - mock.call('High voltage warning: chassis 1 vsensor 2 current voltage 3mV, high threshold 2mV') + mock.call("Failed to update voltage_sensor status for chassis 1 voltage_sensor 1 - Exception('Failed to get voltage',)"), + mock.call('High voltage warning: chassis 1 voltage_sensor 2 current voltage 3mV, high threshold 2mV') ] assert voltage_updater.log_warning.mock_calls == expected_calls - def test_update_module_vsensors(self): + def test_update_module_voltage_sensors(self): chassis = MockChassis() - chassis.make_module_vsensor() + chassis.make_module_voltage_sensor() chassis.set_modular_chassis(True) voltage_updater = sensormond.VoltageUpdater(chassis) voltage_updater.update() - assert len(voltage_updater.module_vsensors) == 1 + assert len(voltage_updater.module_voltage_sensors) == 1 chassis._module_list = [] voltage_updater.update() - assert len(voltage_updater.module_vsensors) == 0 + assert len(voltage_updater.module_voltage_sensors) == 0 class TestCurrentUpdater(object): @@ -224,38 +212,38 @@ def test_deinit(self): def test_over_current(self): chassis = MockChassis() - chassis.make_over_threshold_isensor() + chassis.make_over_threshold_current_sensor() current_updater = sensormond.CurrentUpdater(chassis) current_updater.update() - isensor_list = chassis.get_all_isensors() + current_sensor_list = chassis.get_all_current_sensors() assert current_updater.log_warning.call_count == 1 - current_updater.log_warning.assert_called_with('High Current warning: chassis 1 isensor 1 current Current 3mA, high threshold 2mA') + current_updater.log_warning.assert_called_with('High Current warning: chassis 1 current_sensor 1 current Current 3mA, high threshold 2mA') - isensor_list[0].make_normal_value() + current_sensor_list[0].make_normal_value() current_updater.update() assert current_updater.log_notice.call_count == 1 - current_updater.log_notice.assert_called_with('High Current warning cleared: chassis 1 isensor 1 current restored to 2mA, high threshold 3mA') + current_updater.log_notice.assert_called_with('High Current warning cleared: chassis 1 current_sensor 1 current restored to 2mA, high threshold 3mA') def test_under_current(self): chassis = MockChassis() - chassis.make_under_threshold_isensor() + chassis.make_under_threshold_current_sensor() current_updater = sensormond.CurrentUpdater(chassis) current_updater.update() - isensor_list = chassis.get_all_isensors() + current_sensor_list = chassis.get_all_current_sensors() assert current_updater.log_warning.call_count == 1 - current_updater.log_warning.assert_called_with('Low current warning: chassis 1 isensor 1 current current 1mA, low threshold 2mA') + current_updater.log_warning.assert_called_with('Low current warning: chassis 1 current_sensor 1 current current 1mA, low threshold 2mA') - isensor_list[0].make_normal_value() + current_sensor_list[0].make_normal_value() current_updater.update() assert current_updater.log_notice.call_count == 1 - current_updater.log_notice.assert_called_with('Low current warning cleared: chassis 1 isensor 1 current restored to 2mA, low threshold 1mA') + current_updater.log_notice.assert_called_with('Low current warning cleared: chassis 1 current_sensor 1 current restored to 2mA, low threshold 1mA') - def test_update_isensor_with_exception(self): + def test_update_current_sensor_with_exception(self): chassis = MockChassis() - chassis.make_error_isensor() - isensor = MockIsensor() - isensor.make_over_threshold() - chassis.get_all_isensors().append(isensor) + chassis.make_error_current_sensor() + current_sensor = MockCurrentSensor() + current_sensor.make_over_threshold() + chassis.get_all_current_sensors().append(current_sensor) current_updater = sensormond.CurrentUpdater(chassis) current_updater.update() @@ -263,32 +251,32 @@ def test_update_isensor_with_exception(self): if sys.version_info.major == 3: expected_calls = [ - mock.call("Failed to update isensor status for chassis 1 isensor 1 - Exception('Failed to get current')"), - mock.call('High Current warning: chassis 1 isensor 2 current Current 3mA, high threshold 2mA') + mock.call("Failed to update current_sensor status for chassis 1 current_sensor 1 - Exception('Failed to get current')"), + mock.call('High Current warning: chassis 1 current_sensor 2 current Current 3mA, high threshold 2mA') ] else: expected_calls = [ - mock.call("Failed to update isensor status for chassis 1 isensor 1 - Exception('Failed to get current',)"), - mock.call('High Current warning: chassis 1 isensor 2 current Current 3mA, high threshold 2mA') + mock.call("Failed to update current_sensor status for chassis 1 current_sensor 1 - Exception('Failed to get current',)"), + mock.call('High Current warning: chassis 1 current_sensor 2 current Current 3mA, high threshold 2mA') ] assert current_updater.log_warning.mock_calls == expected_calls - def test_update_module_isensors(self): + def test_update_module_current_sensors(self): chassis = MockChassis() - chassis.make_module_isensor() + chassis.make_module_current_sensor() chassis.set_modular_chassis(True) current_updater = sensormond.CurrentUpdater(chassis) current_updater.update() - assert len(current_updater.module_isensors) == 1 + assert len(current_updater.module_current_sensors) == 1 chassis._module_list = [] current_updater.update() - assert len(current_updater.module_isensors) == 0 + assert len(current_updater.module_current_sensors) == 0 # Modular chassis-related tests -def test_updater_vsensor_check_modular_chassis(): +def test_updater_voltage_sensor_check_modular_chassis(): chassis = MockChassis() assert chassis.is_modular_chassis() == False @@ -307,41 +295,41 @@ def test_updater_vsensor_check_modular_chassis(): assert voltage_updater.chassis_table.table_name == '{}_{}'.format(VOLTAGE_INFO_TABLE_NAME, str(my_slot)) -def test_updater_vsensor_check_chassis_table(): +def test_updater_voltage_sensor_check_chassis_table(): chassis = MockChassis() - vsensor1 = MockVsensor() - chassis.get_all_vsensors().append(vsensor1) + voltage_sensor1 = MockVoltageSensor() + chassis.get_all_voltage_sensors().append(voltage_sensor1) chassis.set_modular_chassis(True) chassis.set_my_slot(1) voltage_updater = sensormond.VoltageUpdater(chassis) voltage_updater.update() - assert voltage_updater.chassis_table.get_size() == chassis.get_num_vsensors() + assert voltage_updater.chassis_table.get_size() == chassis.get_num_voltage_sensors() - vsensor2 = MockVsensor() - chassis.get_all_vsensors().append(vsensor2) + voltage_sensor2 = MockVoltageSensor() + chassis.get_all_voltage_sensors().append(voltage_sensor2) voltage_updater.update() - assert voltage_updater.chassis_table.get_size() == chassis.get_num_vsensors() + assert voltage_updater.chassis_table.get_size() == chassis.get_num_voltage_sensors() -def test_updater_vsensor_check_min_max(): +def test_updater_voltage_sensor_check_min_max(): chassis = MockChassis() - vsensor = MockVsensor(1) - chassis.get_all_vsensors().append(vsensor) + voltage_sensor = MockVoltageSensor(1) + chassis.get_all_voltage_sensors().append(voltage_sensor) chassis.set_modular_chassis(True) chassis.set_my_slot(1) voltage_updater = sensormond.VoltageUpdater(chassis) voltage_updater.update() - slot_dict = voltage_updater.chassis_table.get(vsensor.get_name()) - assert slot_dict['minimum_voltage'] == str(vsensor.get_minimum_recorded()) - assert slot_dict['maximum_voltage'] == str(vsensor.get_maximum_recorded()) + slot_dict = voltage_updater.chassis_table.get(voltage_sensor.get_name()) + assert slot_dict['minimum_voltage'] == str(voltage_sensor.get_minimum_recorded()) + assert slot_dict['maximum_voltage'] == str(voltage_sensor.get_maximum_recorded()) -def test_updater_isensor_check_modular_chassis(): +def test_updater_current_sensor_check_modular_chassis(): chassis = MockChassis() assert chassis.is_modular_chassis() == False @@ -360,39 +348,39 @@ def test_updater_isensor_check_modular_chassis(): assert current_updater.chassis_table.table_name == '{}_{}'.format(CURRENT_INFO_TABLE_NAME, str(my_slot)) -def test_updater_isensor_check_chassis_table(): +def test_updater_current_sensor_check_chassis_table(): chassis = MockChassis() - isensor1 = MockIsensor() - chassis.get_all_isensors().append(isensor1) + current_sensor1 = MockCurrentSensor() + chassis.get_all_current_sensors().append(current_sensor1) chassis.set_modular_chassis(True) chassis.set_my_slot(1) current_updater = sensormond.CurrentUpdater(chassis) current_updater.update() - assert current_updater.chassis_table.get_size() == chassis.get_num_isensors() + assert current_updater.chassis_table.get_size() == chassis.get_num_current_sensors() - isensor2 = MockIsensor() - chassis.get_all_isensors().append(isensor2) + current_sensor2 = MockCurrentSensor() + chassis.get_all_current_sensors().append(current_sensor2) current_updater.update() - assert current_updater.chassis_table.get_size() == chassis.get_num_isensors() + assert current_updater.chassis_table.get_size() == chassis.get_num_current_sensors() -def test_updater_isensor_check_min_max(): +def test_updater_current_sensor_check_min_max(): chassis = MockChassis() - isensor = MockIsensor(1) - chassis.get_all_isensors().append(isensor) + current_sensor = MockCurrentSensor(1) + chassis.get_all_current_sensors().append(current_sensor) chassis.set_modular_chassis(True) chassis.set_my_slot(1) current_updater = sensormond.CurrentUpdater(chassis) current_updater.update() - slot_dict = current_updater.chassis_table.get(isensor.get_name()) - assert slot_dict['minimum_current'] == str(isensor.get_minimum_recorded()) - assert slot_dict['maximum_current'] == str(isensor.get_maximum_recorded()) + slot_dict = current_updater.chassis_table.get(current_sensor.get_name()) + assert slot_dict['minimum_current'] == str(current_sensor.get_minimum_recorded()) + assert slot_dict['maximum_current'] == str(current_sensor.get_maximum_recorded()) def test_signal_handler(): # Test SIGHUP @@ -479,13 +467,13 @@ def unimplemented_callback(): def test_update_entity_info(): mock_table = mock.MagicMock() - mock_vsensor = MockVsensor() + mock_voltage_sensor = MockVoltageSensor() expected_fvp = sensormond.swsscommon.FieldValuePairs( [('position_in_parent', '1'), ('parent_name', 'Parent Name') ]) - sensormond.update_entity_info(mock_table, 'Parent Name', 'Key Name', mock_vsensor, 1) + sensormond.update_entity_info(mock_table, 'Parent Name', 'Key Name', mock_voltage_sensor, 1) assert mock_table.set.call_count == 1 mock_table.set.assert_called_with('Key Name', expected_fvp) From 97ed30e2c79031390ef25e477c33fdb283e81c7b Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Thu, 17 Aug 2023 23:00:14 -0700 Subject: [PATCH 08/10] Addressed review comments --- sonic-sensormond/scripts/sensormond | 87 ++++++++++------------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/sonic-sensormond/scripts/sensormond b/sonic-sensormond/scripts/sensormond index a8a2a53c0..2ae92c128 100755 --- a/sonic-sensormond/scripts/sensormond +++ b/sonic-sensormond/scripts/sensormond @@ -113,36 +113,33 @@ class SensorStatus(logger.Logger): # -# voltageUpdater ====================================================================== +# SensorUpdater ====================================================================== # -class VoltageUpdater(logger.Logger): - # Voltage information table name in database - VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' +class SensorUpdater(logger.Logger): - def __init__(self, chassis): + def __init__(self, table_name, chassis): ''' - Initializer of VoltageUpdater + Initializer of SensorUpdater + :param table_name: Name of sensor table :param chassis: Object representing a platform chassis ''' - super(VoltageUpdater, self).__init__(SYSLOG_IDENTIFIER) + super(SensorUpdater, self).__init__(SYSLOG_IDENTIFIER) self.chassis = chassis - self.voltage_status_dict = {} state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, self.VOLTAGE_INFO_TABLE_NAME) + self.table = swsscommon.Table(state_db, table_name) self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) self.chassis_table = None self.is_chassis_system = chassis.is_modular_chassis() if self.is_chassis_system: - self.module_voltage_sensors = set() my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) if my_slot != INVALID_SLOT: try: # Modular chassis may not have table CHASSIS_STATE_DB. - table_name = VoltageUpdater.VOLTAGE_INFO_TABLE_NAME+'_'+str(my_slot) + slot_table_name = 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_table = swsscommon.Table(chassis_state_db, slot_table_name) except Exception as e: self.chassis_table = None @@ -171,6 +168,25 @@ class VoltageUpdater(logger.Logger): else: self.log_warning(abnormal_log) +# +# VoltageUpdater ====================================================================== +# +class VoltageUpdater(SensorUpdater): + # Voltage information table name in database + VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' + + def __init__(self, chassis): + ''' + Initializer of VoltageUpdater + :param chassis: Object representing a platform chassis + ''' + super(VoltageUpdater, self).__init__(self.VOLTAGE_INFO_TABLE_NAME, chassis) + + self.voltage_status_dict = {} + + if self.is_chassis_system: + self.module_voltage_sensors = set() + def update(self): ''' Update all voltage information to database @@ -278,9 +294,9 @@ class VoltageUpdater(logger.Logger): self.chassis_table._del(name) # -# currentUpdater ====================================================================== +# CurrentUpdater ====================================================================== # -class CurrentUpdater(logger.Logger): +class CurrentUpdater(SensorUpdater): # Current information table name in database CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' @@ -289,52 +305,11 @@ class CurrentUpdater(logger.Logger): Initializer of CurrentUpdater :param chassis: Object representing a platform chassis ''' - super(CurrentUpdater, self).__init__(SYSLOG_IDENTIFIER) + super(CurrentUpdater, self).__init__(self.CURRENT_INFO_TABLE_NAME, chassis) - self.chassis = chassis self.current_status_dict = {} - state_db = daemon_base.db_connect("STATE_DB") - self.table = swsscommon.Table(state_db, self.CURRENT_INFO_TABLE_NAME) - self.phy_entity_table = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) - self.chassis_table = None - - self.is_chassis_system = chassis.is_modular_chassis() if self.is_chassis_system: self.module_current_sensors = set() - my_slot = try_get(chassis.get_my_slot, INVALID_SLOT) - if my_slot != INVALID_SLOT: - try: - # Modular chassis may not have table CHASSIS_STATE_DB. - table_name = CurrentUpdater.CURRENT_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) - except Exception as e: - self.chassis_table = None - - def __del__(self): - if self.table: - table_keys = self.table.getKeys() - for tk in table_keys: - self.table._del(tk) - if self.is_chassis_system and self.chassis_table is not None: - self.chassis_table._del(tk) - 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) - - def _log_on_status_changed(self, normal_status, normal_log, abnormal_log): - ''' - Log when any status changed - :param normal_status: Expected status. - :param normal_log: Log string for expected status. - :param abnormal_log: Log string for unexpected status - :return: - ''' - if normal_status: - self.log_notice(normal_log) - else: - self.log_warning(abnormal_log) def update(self): ''' From 1292ae5af1afdcf9189d11af278fbda8bb9e7490 Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Mon, 21 Aug 2023 22:38:39 -0700 Subject: [PATCH 09/10] Fixed unit test dependency on platform-common changes for sensormond --- sonic-sensormond/tests/mock_platform.py | 30 +++++++++++++++---- .../swsscommon.py => mock_swsscommon.py} | 0 .../tests/mocked_libs/swsscommon/__init__.py | 5 ---- sonic-sensormond/tests/test_sensormond.py | 15 ++++++++-- 4 files changed, 37 insertions(+), 13 deletions(-) rename sonic-sensormond/tests/{mocked_libs/swsscommon/swsscommon.py => mock_swsscommon.py} (100%) delete mode 100644 sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py diff --git a/sonic-sensormond/tests/mock_platform.py b/sonic-sensormond/tests/mock_platform.py index 4ea05c6ea..ce6b0936b 100644 --- a/sonic-sensormond/tests/mock_platform.py +++ b/sonic-sensormond/tests/mock_platform.py @@ -1,10 +1,8 @@ from sonic_platform_base import chassis_base from sonic_platform_base import module_base -from sonic_platform_base import sensor_base -class MockVoltageSensor(sensor_base.VoltageSensorBase): +class MockVoltageSensor(): def __init__(self, index=None): - super(MockVoltageSensor, self).__init__() self._name = 'Voltage sensor {}'.format(index) if index != None else None self._presence = True self._model = 'Voltage sensor model' @@ -85,9 +83,8 @@ def get_position_in_parent(self): def is_replaceable(self): return self._replaceable -class MockCurrentSensor(sensor_base.CurrentSensorBase): +class MockCurrentSensor(): def __init__(self, index=None): - super(MockCurrentSensor, self).__init__() self._name = 'Current sensor {}'.format(index) if index != None else None self._presence = True self._model = 'Current sensor model' @@ -186,10 +183,24 @@ def __init__(self): self._status = True self._position_in_parent = 1 self._replaceable = False + self._current_sensor_list = [] + self._voltage_sensor_list = [] self._is_chassis_system = False self._my_slot = module_base.ModuleBase.MODULE_INVALID_SLOT + def get_num_voltage_sensors(self): + return len(self._voltage_sensor_list) + + def get_num_current_sensors(self): + return len(self._current_sensor_list) + + def get_all_voltage_sensors(self): + return self._voltage_sensor_list + + def get_all_current_sensors(self): + return self._current_sensor_list + def make_over_threshold_voltage_sensor(self): voltage_sensor = MockVoltageSensor() voltage_sensor.make_over_threshold() @@ -272,3 +283,12 @@ def is_replaceable(self): class MockModule(module_base.ModuleBase): def __init__(self): super(MockModule, self).__init__() + self._current_sensor_list = [] + self._voltage_sensor_list = [] + + def get_all_voltage_sensors(self): + return self._voltage_sensor_list + + def get_all_current_sensors(self): + return self._current_sensor_list + diff --git a/sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py b/sonic-sensormond/tests/mock_swsscommon.py similarity index 100% rename from sonic-sensormond/tests/mocked_libs/swsscommon/swsscommon.py rename to sonic-sensormond/tests/mock_swsscommon.py diff --git a/sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py b/sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py deleted file mode 100644 index 012af621e..000000000 --- a/sonic-sensormond/tests/mocked_libs/swsscommon/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -''' - Mock implementation of swsscommon package for unit testing -''' - -from . import swsscommon diff --git a/sonic-sensormond/tests/test_sensormond.py b/sonic-sensormond/tests/test_sensormond.py index 524ba54ad..7a6f34886 100644 --- a/sonic-sensormond/tests/test_sensormond.py +++ b/sonic-sensormond/tests/test_sensormond.py @@ -5,6 +5,7 @@ from unittest import mock import pytest from sonic_py_common import daemon_base +from swsscommon import swsscommon # Setup load paths for mocked modules @@ -17,19 +18,20 @@ # Import mocked modules -from swsscommon.swsscommon import Table +from .mock_swsscommon import Table, FieldValuePairs from .mock_platform import MockChassis, MockVoltageSensor, MockCurrentSensor # Load file under test load_source('sensormond', os.path.join(scripts_path, 'sensormond')) import sensormond +daemon_base.db_connect = mock.MagicMock() +swsscommon.Table = Table +swsscommon.FieldValuePairs = FieldValuePairs VOLTAGE_INFO_TABLE_NAME = 'VOLTAGE_INFO' CURRENT_INFO_TABLE_NAME = 'CURRENT_INFO' -daemon_base.db_connect = mock.MagicMock() - @pytest.fixture(scope='function', autouse=True) def configure_mocks(): sensormond.SensorStatus.log_notice = mock.MagicMock() @@ -437,6 +439,13 @@ def test_signal_handler(): def test_daemon_run(): + + import sonic_platform.platform + class MyPlatform(): + def get_chassis(self): + return MockChassis() + sonic_platform.platform.Platform = MyPlatform + daemon_sensormond = sensormond.SensorMonitorDaemon() daemon_sensormond.stop_event.wait = mock.MagicMock(return_value=True) ret = daemon_sensormond.run() From 05799dace85df2f56f1fb08918f3b18f981872ad Mon Sep 17 00:00:00 2001 From: Mridul Bajpai Date: Tue, 29 Aug 2023 15:24:50 -0700 Subject: [PATCH 10/10] Addressed review comments. --- sonic-sensormond/scripts/sensormond | 38 ++++++++++++++----------- sonic-sensormond/tests/mock_platform.py | 6 ++++ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/sonic-sensormond/scripts/sensormond b/sonic-sensormond/scripts/sensormond index 2ae92c128..da8bc1bda 100755 --- a/sonic-sensormond/scripts/sensormond +++ b/sonic-sensormond/scripts/sensormond @@ -237,10 +237,12 @@ class VoltageUpdater(SensorUpdater): low_critical_threshold = NOT_AVAILABLE maximum_voltage = NOT_AVAILABLE minimum_voltage = NOT_AVAILABLE + unit = NOT_AVAILABLE voltage = try_get(voltage_sensor.get_value) is_replaceable = try_get(voltage_sensor.is_replaceable, False) if voltage != NOT_AVAILABLE: voltage_status.set_value(name, voltage) + unit = try_get(voltage_sensor.get_unit) minimum_voltage = try_get(voltage_sensor.get_minimum_recorded) maximum_voltage = try_get(voltage_sensor.get_maximum_recorded) high_threshold = try_get(voltage_sensor.get_high_threshold) @@ -251,24 +253,25 @@ class VoltageUpdater(SensorUpdater): warning = False if voltage != NOT_AVAILABLE and voltage_status.set_over_threshold(voltage, high_threshold): self._log_on_status_changed(not voltage_status.over_threshold, - 'High voltage warning cleared: {} voltage restored to {}mV, high threshold {}mV'. - format(name, voltage, high_threshold), - 'High voltage warning: {} current voltage {}mV, high threshold {}mV'. - format(name, voltage, high_threshold) + 'High voltage warning cleared: {} voltage restored to {}{}, high threshold {}{}'. + format(name, voltage, unit, high_threshold, unit), + 'High voltage warning: {} current voltage {}{}, high threshold {}{}'. + format(name, voltage, unit, high_threshold, unit) ) warning = warning | voltage_status.over_threshold if voltage != NOT_AVAILABLE and voltage_status.set_under_threshold(voltage, low_threshold): self._log_on_status_changed(not voltage_status.under_threshold, - 'Low voltage warning cleared: {} voltage restored to {}mV, low threshold {}mV'. - format(name, voltage, low_threshold), - 'Low voltage warning: {} current voltage {}mV, low threshold {}mV'. - format(name, voltage, low_threshold) + 'Low voltage warning cleared: {} voltage restored to {}{}, low threshold {}{}'. + format(name, voltage, unit, low_threshold, unit), + 'Low voltage warning: {} current voltage {}{}, low threshold {}{}'. + format(name, voltage, unit, low_threshold, unit) ) warning = warning | voltage_status.under_threshold fvs = swsscommon.FieldValuePairs( [('voltage', str(voltage)), + ('unit', unit), ('minimum_voltage', str(minimum_voltage)), ('maximum_voltage', str(maximum_voltage)), ('high_threshold', str(high_threshold)), @@ -355,6 +358,7 @@ class CurrentUpdater(SensorUpdater): current_status = self.current_status_dict[name] + unit = NOT_AVAILABLE high_threshold = NOT_AVAILABLE low_threshold = NOT_AVAILABLE high_critical_threshold = NOT_AVAILABLE @@ -365,6 +369,7 @@ class CurrentUpdater(SensorUpdater): is_replaceable = try_get(current_sensor.is_replaceable, False) if current != NOT_AVAILABLE: current_status.set_value(name, current) + unit = try_get(current_sensor.get_unit) minimum_current = try_get(current_sensor.get_minimum_recorded) maximum_current = try_get(current_sensor.get_maximum_recorded) high_threshold = try_get(current_sensor.get_high_threshold) @@ -375,24 +380,25 @@ class CurrentUpdater(SensorUpdater): warning = False if current != NOT_AVAILABLE and current_status.set_over_threshold(current, high_threshold): self._log_on_status_changed(not current_status.over_threshold, - 'High Current warning cleared: {} current restored to {}mA, high threshold {}mA'. - format(name, current, high_threshold), - 'High Current warning: {} current Current {}mA, high threshold {}mA'. - format(name, current, high_threshold) + 'High Current warning cleared: {} current restored to {}{}, high threshold {}{}'. + format(name, current, unit, high_threshold, unit), + 'High Current warning: {} current Current {}{}, high threshold {}{}'. + format(name, current, unit, high_threshold, unit) ) warning = warning | current_status.over_threshold if current != NOT_AVAILABLE and current_status.set_under_threshold(current, low_threshold): self._log_on_status_changed(not current_status.under_threshold, - 'Low current warning cleared: {} current restored to {}mA, low threshold {}mA'. - format(name, current, low_threshold), - 'Low current warning: {} current current {}mA, low threshold {}mA'. - format(name, current, low_threshold) + 'Low current warning cleared: {} current restored to {}{}, low threshold {}{}'. + format(name, current, unit, low_threshold, unit), + 'Low current warning: {} current current {}{}, low threshold {}{}'. + format(name, current, unit, low_threshold, unit) ) warning = warning | current_status.under_threshold fvs = swsscommon.FieldValuePairs( [('current', str(current)), + ('unit', unit), ('minimum_current', str(minimum_current)), ('maximum_current', str(maximum_current)), ('high_threshold', str(high_threshold)), diff --git a/sonic-sensormond/tests/mock_platform.py b/sonic-sensormond/tests/mock_platform.py index ce6b0936b..146c1aff9 100644 --- a/sonic-sensormond/tests/mock_platform.py +++ b/sonic-sensormond/tests/mock_platform.py @@ -22,6 +22,9 @@ def __init__(self, index=None): def get_value(self): return self._value + def get_unit(self): + return "mV" + def get_minimum_recorded(self): return self._minimum_value @@ -104,6 +107,9 @@ def __init__(self, index=None): def get_value(self): return self._value + def get_unit(self): + return "mA" + def get_minimum_recorded(self): return self._minimum_value