fe59e0f2c0
- Why I did it In SONiC thermal control algorithm, it compares thermal zone temperature with thermal zone threshold. Previously, a thermal zone with no thermal sensor can still get its threshold. However, a recently driver patch changes this behavior: a thermal zone with no thermal sensor will return 0 for threshold. We need to ignore such thermal zone. - How I did it Ignore thermal zones whose temperature is 0. - How to verify it Added unit test case and Manual test
337 lines
17 KiB
Python
337 lines
17 KiB
Python
#
|
|
# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES.
|
|
# Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
import glob
|
|
import os
|
|
import pytest
|
|
import sys
|
|
if sys.version_info.major == 3:
|
|
from unittest import mock
|
|
else:
|
|
import mock
|
|
|
|
test_path = os.path.dirname(os.path.abspath(__file__))
|
|
modules_path = os.path.dirname(test_path)
|
|
sys.path.insert(0, modules_path)
|
|
|
|
from sonic_platform.chassis import Chassis
|
|
from sonic_platform.device_data import DeviceDataManager
|
|
|
|
|
|
class TestThermal:
|
|
@mock.patch('os.path.exists', mock.MagicMock(return_value=True))
|
|
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_gearbox_count', mock.MagicMock(return_value=2))
|
|
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_cpu_thermal_count', mock.MagicMock(return_value=2))
|
|
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_platform_name', mock.MagicMock(return_value='x86_64-mlnx_msn2700-r0'))
|
|
def test_chassis_thermal(self):
|
|
from sonic_platform.thermal import THERMAL_NAMING_RULE
|
|
os.path.exists = mock.MagicMock(return_value=True)
|
|
chassis = Chassis()
|
|
thermal_list = chassis.get_all_thermals()
|
|
assert thermal_list
|
|
thermal_dict = {thermal.get_name(): thermal for thermal in thermal_list}
|
|
gearbox_thermal_rule = None
|
|
cpu_thermal_rule = None
|
|
for rule in THERMAL_NAMING_RULE['chassis thermals']:
|
|
thermal_type = rule.get('type', 'single')
|
|
if thermal_type == 'single':
|
|
thermal_name = rule['name']
|
|
if rule['temperature'] == 'comex_amb':
|
|
assert thermal_name not in thermal_dict
|
|
continue
|
|
default_present = rule.get('default_present', True)
|
|
if not default_present:
|
|
assert thermal_name not in thermal_dict
|
|
continue
|
|
assert thermal_name in thermal_dict
|
|
thermal = thermal_dict[thermal_name]
|
|
assert rule['temperature'] in thermal.temperature
|
|
assert rule['high_threshold'] in thermal.high_threshold if 'high_threshold' in rule else thermal.high_threshold is None
|
|
assert rule['high_critical_threshold'] in thermal.high_critical_threshold if 'high_critical_threshold' in rule else thermal.high_critical_threshold is None
|
|
else:
|
|
if 'Gearbox' in rule['name']:
|
|
gearbox_thermal_rule = rule
|
|
elif 'CPU Core' in rule['name']:
|
|
cpu_thermal_rule = rule
|
|
|
|
gearbox_thermal_count = 0
|
|
cpu_thermal_count = 0
|
|
for thermal in thermal_list:
|
|
if 'Gearbox' in thermal.get_name():
|
|
start_index = gearbox_thermal_rule.get('start_index', 1)
|
|
start_index += gearbox_thermal_count
|
|
assert thermal.get_name() == gearbox_thermal_rule['name'].format(start_index)
|
|
assert gearbox_thermal_rule['temperature'].format(start_index) in thermal.temperature
|
|
assert gearbox_thermal_rule['high_threshold'].format(start_index) in thermal.high_threshold
|
|
assert gearbox_thermal_rule['high_critical_threshold'].format(start_index) in thermal.high_critical_threshold
|
|
gearbox_thermal_count += 1
|
|
elif 'CPU Core' in thermal.get_name():
|
|
start_index = cpu_thermal_rule.get('start_index', 1)
|
|
start_index += cpu_thermal_count
|
|
assert thermal.get_name() == cpu_thermal_rule['name'].format(start_index)
|
|
assert cpu_thermal_rule['temperature'].format(start_index) in thermal.temperature
|
|
assert cpu_thermal_rule['high_threshold'].format(start_index) in thermal.high_threshold
|
|
assert cpu_thermal_rule['high_critical_threshold'].format(start_index) in thermal.high_critical_threshold
|
|
cpu_thermal_count += 1
|
|
|
|
assert gearbox_thermal_count == 2
|
|
assert cpu_thermal_count == 2
|
|
|
|
def test_chassis_thermal_includes(self):
|
|
from sonic_platform.thermal import THERMAL_NAMING_RULE
|
|
DeviceDataManager.get_platform_name = mock.MagicMock(return_value='x86_64-nvidia_sn2201-r0')
|
|
DeviceDataManager.get_thermal_capability = mock.MagicMock(return_value={'comex_amb': False, 'cpu_amb': True, 'swb_amb': True})
|
|
chassis = Chassis()
|
|
thermal_list = chassis.get_all_thermals()
|
|
assert thermal_list
|
|
thermal_dict = {thermal.get_name(): thermal for thermal in thermal_list}
|
|
for rule in THERMAL_NAMING_RULE['chassis thermals']:
|
|
default_present = rule.get('default_present', True)
|
|
if not default_present:
|
|
thermal_name = rule['name']
|
|
assert thermal_name in thermal_dict
|
|
|
|
def test_psu_thermal(self):
|
|
from sonic_platform.thermal import initialize_psu_thermal, THERMAL_NAMING_RULE
|
|
os.path.exists = mock.MagicMock(return_value=True)
|
|
presence_cb = mock.MagicMock(return_value=(True, ''))
|
|
thermal_list = initialize_psu_thermal(0, presence_cb)
|
|
assert len(thermal_list) == 1
|
|
thermal = thermal_list[0]
|
|
rule = THERMAL_NAMING_RULE['psu thermals']
|
|
start_index = rule.get('start_index', 1)
|
|
assert thermal.get_name() == rule['name'].format(start_index)
|
|
assert rule['temperature'].format(start_index) in thermal.temperature
|
|
assert rule['high_threshold'].format(start_index) in thermal.high_threshold
|
|
assert thermal.high_critical_threshold is None
|
|
assert thermal.get_position_in_parent() == 1
|
|
assert thermal.is_replaceable() == False
|
|
|
|
presence_cb = mock.MagicMock(return_value=(False, 'Not present'))
|
|
thermal_list = initialize_psu_thermal(0, presence_cb)
|
|
assert len(thermal_list) == 1
|
|
thermal = thermal_list[0]
|
|
assert thermal.get_temperature() is None
|
|
assert thermal.get_high_threshold() is None
|
|
assert thermal.get_high_critical_threshold() is None
|
|
|
|
def test_sfp_thermal(self):
|
|
from sonic_platform.thermal import initialize_sfp_thermal, THERMAL_NAMING_RULE
|
|
os.path.exists = mock.MagicMock(return_value=True)
|
|
thermal_list = initialize_sfp_thermal(0)
|
|
assert len(thermal_list) == 1
|
|
thermal = thermal_list[0]
|
|
rule = THERMAL_NAMING_RULE['sfp thermals']
|
|
start_index = rule.get('start_index', 1)
|
|
assert thermal.get_name() == rule['name'].format(start_index)
|
|
assert rule['temperature'].format(start_index) in thermal.temperature
|
|
assert rule['high_threshold'].format(start_index) in thermal.high_threshold
|
|
assert rule['high_critical_threshold'].format(start_index) in thermal.high_critical_threshold
|
|
assert thermal.get_position_in_parent() == 1
|
|
assert thermal.is_replaceable() == False
|
|
|
|
def test_get_temperature(self):
|
|
from sonic_platform.thermal import Thermal
|
|
from sonic_platform import utils
|
|
thermal = Thermal('test', 'temp_file', None, None, 1)
|
|
utils.read_float_from_file = mock.MagicMock(return_value=35727)
|
|
assert thermal.get_temperature() == 35.727
|
|
|
|
utils.read_float_from_file = mock.MagicMock(return_value=0.0)
|
|
assert thermal.get_temperature() is None
|
|
|
|
utils.read_float_from_file = mock.MagicMock(return_value=None)
|
|
assert thermal.get_temperature() is None
|
|
|
|
def test_get_high_threshold(self):
|
|
from sonic_platform.thermal import Thermal
|
|
from sonic_platform import utils
|
|
thermal = Thermal('test', None, None, None, 1)
|
|
assert thermal.get_high_threshold() is None
|
|
|
|
thermal.high_threshold = 'high_th_file'
|
|
utils.read_float_from_file = mock.MagicMock(return_value=25833)
|
|
assert thermal.get_temperature() == 25.833
|
|
|
|
utils.read_float_from_file = mock.MagicMock(return_value=0.0)
|
|
assert thermal.get_temperature() is None
|
|
|
|
utils.read_float_from_file = mock.MagicMock(return_value=None)
|
|
assert thermal.get_temperature() is None
|
|
|
|
def test_get_high_critical_threshold(self):
|
|
from sonic_platform.thermal import Thermal
|
|
from sonic_platform import utils
|
|
thermal = Thermal('test', None, None, None, 1)
|
|
assert thermal.get_high_critical_threshold() is None
|
|
|
|
thermal.high_critical_threshold = 'high_th_file'
|
|
utils.read_float_from_file = mock.MagicMock(return_value=120839)
|
|
assert thermal.get_high_critical_threshold() == 120.839
|
|
|
|
utils.read_float_from_file = mock.MagicMock(return_value=0.0)
|
|
assert thermal.get_high_critical_threshold() is None
|
|
|
|
utils.read_float_from_file = mock.MagicMock(return_value=None)
|
|
assert thermal.get_high_critical_threshold() is None
|
|
|
|
def test_set_thermal_algorithm_status(self):
|
|
from sonic_platform.thermal import Thermal, THERMAL_ZONE_FOLDER_WILDCARD, THERMAL_ZONE_POLICY_FILE, THERMAL_ZONE_MODE_FILE
|
|
from sonic_platform import utils
|
|
glob.iglob = mock.MagicMock(return_value=['thermal_zone1', 'thermal_zone2'])
|
|
utils.write_file = mock.MagicMock()
|
|
assert Thermal.set_thermal_algorithm_status(True, False)
|
|
|
|
for folder in glob.iglob(THERMAL_ZONE_FOLDER_WILDCARD):
|
|
utils.write_file.assert_any_call(os.path.join(folder, THERMAL_ZONE_POLICY_FILE), 'step_wise')
|
|
utils.write_file.assert_any_call(os.path.join(folder, THERMAL_ZONE_MODE_FILE), 'enabled')
|
|
|
|
assert Thermal.set_thermal_algorithm_status(False, False)
|
|
for folder in glob.iglob(THERMAL_ZONE_FOLDER_WILDCARD):
|
|
utils.write_file.assert_any_call(os.path.join(folder, THERMAL_ZONE_POLICY_FILE), 'user_space')
|
|
utils.write_file.assert_any_call(os.path.join(folder, THERMAL_ZONE_MODE_FILE), 'disabled')
|
|
|
|
assert not Thermal.set_thermal_algorithm_status(False, False)
|
|
|
|
assert Thermal.set_thermal_algorithm_status(False)
|
|
|
|
@mock.patch('glob.iglob', mock.MagicMock(return_value=['thermal_zone1', 'thermal_zone2']))
|
|
@mock.patch('sonic_platform.utils.read_int_from_file')
|
|
def test_get_min_allowed_cooling_level_by_thermal_zone(self, mock_read_file):
|
|
from sonic_platform.thermal import Thermal, THERMAL_ZONE_TEMP_FILE, THERMAL_ZONE_HIGH_THRESHOLD, THERMAL_ZONE_NORMAL_THRESHOLD, MIN_COOLING_LEVEL_FOR_HIGH, MIN_COOLING_LEVEL_FOR_NORMAL
|
|
mock_read_file.side_effect = Exception('')
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() is None
|
|
|
|
mock_file_content = {}
|
|
def mock_read_int_from_file(file_path, default=0, raise_exception=False):
|
|
return mock_file_content[file_path]
|
|
|
|
mock_read_file.side_effect = mock_read_int_from_file
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_NORMAL_THRESHOLD)] = 75000
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_HIGH_THRESHOLD)] = 85000
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 69000
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_NORMAL_THRESHOLD)] = 75000
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_HIGH_THRESHOLD)] = 85000
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_TEMP_FILE)] = 24000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() == MIN_COOLING_LEVEL_FOR_NORMAL
|
|
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 71000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() == MIN_COOLING_LEVEL_FOR_HIGH
|
|
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 79000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() == MIN_COOLING_LEVEL_FOR_HIGH
|
|
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 81000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() is None
|
|
|
|
@mock.patch('glob.iglob', mock.MagicMock(return_value=['thermal_zone1', 'thermal_zone2']))
|
|
@mock.patch('sonic_platform.utils.read_int_from_file')
|
|
def test_no_sensor_thermal_zone(self, mock_read_file):
|
|
from sonic_platform.thermal import Thermal, THERMAL_ZONE_TEMP_FILE, THERMAL_ZONE_HIGH_THRESHOLD, THERMAL_ZONE_NORMAL_THRESHOLD, MIN_COOLING_LEVEL_FOR_HIGH, MIN_COOLING_LEVEL_FOR_NORMAL
|
|
|
|
mock_file_content = {}
|
|
def mock_read_int_from_file(file_path, **kwargs):
|
|
return mock_file_content[file_path]
|
|
|
|
mock_read_file.side_effect = mock_read_int_from_file
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_NORMAL_THRESHOLD)] = 0
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_HIGH_THRESHOLD)] = 0
|
|
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 0
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_NORMAL_THRESHOLD)] = 75000
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_HIGH_THRESHOLD)] = 85000
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_TEMP_FILE)] = 24000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() == MIN_COOLING_LEVEL_FOR_NORMAL
|
|
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_TEMP_FILE)] = 71000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() == MIN_COOLING_LEVEL_FOR_HIGH
|
|
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_TEMP_FILE)] = 79000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() == MIN_COOLING_LEVEL_FOR_HIGH
|
|
|
|
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_TEMP_FILE)] = 81000
|
|
assert Thermal.get_min_allowed_cooling_level_by_thermal_zone() is None
|
|
|
|
def test_check_module_temperature_trustable(self):
|
|
from sonic_platform.thermal import Thermal
|
|
from sonic_platform import utils
|
|
glob.iglob = mock.MagicMock(return_value=['thermal_zone1', 'thermal_zone2'])
|
|
|
|
utils.read_int_from_file = mock.MagicMock(return_value=1)
|
|
assert Thermal.check_module_temperature_trustable() == 'untrust'
|
|
|
|
utils.read_int_from_file = mock.MagicMock(return_value=0)
|
|
assert Thermal.check_module_temperature_trustable() == 'trust'
|
|
|
|
def test_get_min_amb_temperature(self):
|
|
from sonic_platform.thermal import Thermal, MAX_AMBIENT_TEMP, CHASSIS_THERMAL_SYSFS_FOLDER
|
|
from sonic_platform import utils
|
|
|
|
utils.read_int_from_file = mock.MagicMock(side_effect=Exception(''))
|
|
assert Thermal.get_min_amb_temperature() == MAX_AMBIENT_TEMP
|
|
|
|
mock_file_content = {}
|
|
def mock_read_int_from_file(file_path, default=0, raise_exception=False):
|
|
return mock_file_content[file_path]
|
|
|
|
utils.read_int_from_file = mock_read_int_from_file
|
|
mock_file_content[os.path.join(CHASSIS_THERMAL_SYSFS_FOLDER, 'fan_amb')] = 50
|
|
mock_file_content[os.path.join(CHASSIS_THERMAL_SYSFS_FOLDER, 'port_amb')] = 40
|
|
assert Thermal.get_min_amb_temperature() == 40
|
|
|
|
@mock.patch('sonic_platform.utils.write_file')
|
|
def test_set_cooling_level(self, mock_write_file):
|
|
from sonic_platform.thermal import Thermal, COOLING_STATE_PATH
|
|
Thermal.set_cooling_level(10)
|
|
calls = [mock.call(COOLING_STATE_PATH, 20, raise_exception=True)]
|
|
mock_write_file.assert_has_calls(calls)
|
|
|
|
pre_call_count = mock_write_file.call_count
|
|
Thermal.set_cooling_level(10)
|
|
assert pre_call_count == mock_write_file.call_count
|
|
|
|
Thermal.set_cooling_level(9)
|
|
calls = [mock.call(COOLING_STATE_PATH, 19, raise_exception=True)]
|
|
mock_write_file.assert_has_calls(calls)
|
|
|
|
@mock.patch('sonic_platform.utils.write_file')
|
|
def test_set_cooling_state(self, mock_write_file):
|
|
from sonic_platform.thermal import Thermal, COOLING_STATE_PATH
|
|
Thermal.set_cooling_state(10)
|
|
calls = [mock.call(COOLING_STATE_PATH, 10, raise_exception=True)]
|
|
mock_write_file.assert_has_calls(calls)
|
|
|
|
pre_call_count = mock_write_file.call_count
|
|
Thermal.set_cooling_state(10)
|
|
assert pre_call_count == mock_write_file.call_count
|
|
|
|
Thermal.set_cooling_state(9)
|
|
calls = [mock.call(COOLING_STATE_PATH, 9, raise_exception=True)]
|
|
mock_write_file.assert_has_calls(calls)
|
|
|
|
@mock.patch('sonic_platform.utils.read_int_from_file')
|
|
def test_get_cooling_level(self, mock_read_file):
|
|
from sonic_platform.thermal import Thermal, COOLING_STATE_PATH
|
|
Thermal.get_cooling_level()
|
|
mock_read_file.assert_called_with(COOLING_STATE_PATH, raise_exception=True)
|
|
|
|
mock_read_file.side_effect = IOError('')
|
|
with pytest.raises(RuntimeError):
|
|
Thermal.get_cooling_level()
|
|
|
|
mock_read_file.side_effect = ValueError('')
|
|
with pytest.raises(RuntimeError):
|
|
Thermal.get_cooling_level() |