diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/thermal_updater.py b/platform/mellanox/mlnx-platform-api/sonic_platform/thermal_updater.py index 48ad612590e..508968b35f1 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/thermal_updater.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/thermal_updater.py @@ -1,6 +1,6 @@ # # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ from . import utils from sonic_py_common import logger +import re import sys import time @@ -55,26 +56,78 @@ def __init__(self, sfp_list, update_asic=True): self._timer = utils.Timer() self._update_asic = update_asic + def wait_for_sysfs_nodes(self): + """ + Wait for temperature sysfs nodes to be present before proceeding. + Returns: + bool: True if wait success else timeout + """ + start_time = time.time() + logger.log_notice('Waiting for temperature sysfs nodes to be present...') + conditions = [] + + # ASIC temperature sysfs node + asic_count = DeviceDataManager.get_asic_count() + for asic_index in range(asic_count): + conditions.append(lambda idx=asic_index: os.path.exists(f'/sys/module/sx_core/asic{idx}/temperature/input')) + + # Module temperature sysfs nodes + sfp_count = len(self._sfp_list) if self._sfp_list else 0 + result = DeviceDataManager.wait_sysfs_ready(sfp_count) + end_time = time.time() + elapsed_time = end_time - start_time + + if result: + logger.log_notice(f'Temperature sysfs nodes are ready. Wait time: {elapsed_time:.4f} seconds') + else: + logger.log_error(f'Timeout waiting for temperature sysfs nodes. Wait time: {elapsed_time:.4f} seconds') + + return result + + def _find_matching_key(self, dev_parameters, pattern): + """ + Find the first key in dev_parameters that matches the given regex pattern. + Returns the matching key and its value, or (None, None) if no match found. + """ + for key in dev_parameters.keys(): + if re.match(pattern, key): + return key, dev_parameters[key] + return None, None + def load_tc_config(self): asic_poll_interval = 1 sfp_poll_interval = 10 data = utils.load_json_file(TC_CONFIG_FILE, log_func=None) if not data: - logger.log_notice(f'{TC_CONFIG_FILE} does not exist, use default polling interval') + logger.log_error(f'{TC_CONFIG_FILE} does not exist, use default polling interval') if data: dev_parameters = data.get('dev_parameters') - if dev_parameters is not None: - asic_parameter = dev_parameters.get('asic') + if not dev_parameters: + logger.log_error('dev_parameters not configured or empty, using default intervals') + else: + # Find ASIC parameter using regex pattern + asic_key, asic_parameter = self._find_matching_key(dev_parameters, r'asic\\d*') if asic_parameter is not None: asic_poll_interval_config = asic_parameter.get('poll_time') if asic_poll_interval_config: - asic_poll_interval = int(asic_poll_interval_config) / 2 - module_parameter = dev_parameters.get('module\\d+') + asic_poll_interval = int(asic_poll_interval_config) + logger.log_notice(f'ASIC parameter found with key "{asic_key}", poll_time: {asic_poll_interval_config}, interval: {asic_poll_interval}') + else: + logger.log_error(f'ASIC poll_time not configured in "{asic_key}", using default interval: {asic_poll_interval}') + else: + logger.log_error(f'ASIC parameter not found (pattern: asic\\d*), using default interval: {asic_poll_interval}') + # Find Module parameter using regex pattern + module_key, module_parameter = self._find_matching_key(dev_parameters, r'module\\d+') if module_parameter is not None: sfp_poll_interval_config = module_parameter.get('poll_time') if sfp_poll_interval_config: - sfp_poll_interval = int(sfp_poll_interval_config) / 2 + sfp_poll_interval = int(sfp_poll_interval_config) + logger.log_notice(f'Module parameter found with key "{module_key}", poll_time: {sfp_poll_interval_config}, interval: {sfp_poll_interval}') + else: + logger.log_error(f'Module poll_time not configured in "{module_key}", using default interval: {sfp_poll_interval}') + else: + logger.log_error(f'Module parameter not found (pattern: module\\d+), using default interval: {sfp_poll_interval}') if self._update_asic: logger.log_notice(f'ASIC polling interval: {asic_poll_interval}') diff --git a/platform/mellanox/mlnx-platform-api/tests/test_thermal_updater.py b/platform/mellanox/mlnx-platform-api/tests/test_thermal_updater.py index 2b77f5db1ec..5fe16d39083 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_thermal_updater.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_thermal_updater.py @@ -1,6 +1,6 @@ # # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ mock_tc_config = """ { "dev_parameters": { - "asic": { + "asic\\\\d*": { "pwm_min": 20, "pwm_max": 100, "val_min": "!70000", @@ -48,22 +48,58 @@ class TestThermalUpdater: - def test_load_tc_config_non_exists(self): + @mock.patch('sonic_platform.thermal_updater.logger') + def test_load_tc_config_non_exists(self, mock_logger): updater = ThermalUpdater(None) updater.load_tc_config() assert updater._timer._timestamp_queue.qsize() == 2 - def test_load_tc_config_mocked(self): + @mock.patch('sonic_platform.thermal_updater.logger') + def test_load_tc_config_mocked(self, mock_logger): updater = ThermalUpdater(None) mock_os_open = mock.mock_open(read_data=mock_tc_config) with mock.patch('sonic_platform.utils.open', mock_os_open): updater.load_tc_config() assert updater._timer._timestamp_queue.qsize() == 2 + # Verify that debug logs were called with the correct parameters + assert mock_logger.log_notice.call_count >= 2 # At least ASIC and Module parameter logs + def test_find_matching_key(self): + """Test _find_matching_key method for regex pattern matching""" + updater = ThermalUpdater(None) + + # Test with asic pattern - should match 'asic\d*' keys + dev_parameters = { + 'asic\\d*': {'poll_time': 3}, + 'module\\d+': {'poll_time': 20}, + 'sensor_amb': {'poll_time': 30} + } + + # Test matching asic pattern + key, value = updater._find_matching_key(dev_parameters, r'asic\\d*') + assert key == 'asic\\d*' + assert value == {'poll_time': 3} + + # Test matching module pattern + key, value = updater._find_matching_key(dev_parameters, r'module\\d+') + assert key == 'module\\d+' + assert value == {'poll_time': 20} + + # Test non-matching pattern + key, value = updater._find_matching_key(dev_parameters, r'nonexistent') + assert key is None + assert value is None + + # Test with empty dict + key, value = updater._find_matching_key({}, r'asic\\d*') + assert key is None + assert value is None + + @mock.patch('sonic_platform.thermal_updater.logger') @mock.patch('sonic_platform.thermal_updater.ThermalUpdater.update_asic', mock.MagicMock()) @mock.patch('sonic_platform.thermal_updater.ThermalUpdater.update_module', mock.MagicMock()) @mock.patch('sonic_platform.utils.write_file') - def test_start_stop(self, mock_write): + def test_start_stop(self, mock_write, mock_logger): mock_sfp = mock.MagicMock() mock_sfp.sdk_index = 1 updater = ThermalUpdater([mock_sfp])