[Mellanox] Refactor Mellanox platform API to support dynamic port configuration (#8422)

- Why I did it
* To support systems with dynamic port configuration
* Apply lazy initialization to faster the speed of loading platform API

- How I did it
* Add module.py to implement dynamic port configuration (aka line card model)
* Adjust chassis.py, platform.py, thermal.py, sfp.py to support dynamic port configuration
* Optimize existing code

- How to verify it
Platform regression on MSN4700, MSN3800 and MSN2700, 100% pass
Unit test covers all new changes.
This commit is contained in:
Junchao-Mellanox 2021-10-25 12:59:06 +08:00 committed by GitHub
parent d051bc4eb7
commit e8b4c2a1f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 3699 additions and 2074 deletions

7
.gitignore vendored
View File

@ -83,6 +83,13 @@ dockers/**/buildinfo
platform/**/buildinfo
sonic-slave*/**/buildinfo
# pytest coverage files
.coverage
coverage.xml
test-results.xml
htmlcov/
# Dev tools
.vscode/
.idea/

View File

@ -1,2 +1,6 @@
*.pyc
.cache/
*/test-results.xml
*/htmlcov/
*/coverage.xml
*/.coverage

View File

@ -15,5 +15,6 @@
## limitations under the License.
##
[pytest]
addopts = --cov=sonic_platform --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv
filterwarnings =
ignore::DeprecationWarning

File diff suppressed because it is too large Load Diff

View File

@ -14,26 +14,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import glob
import os
from sonic_py_common import device_info
from . import utils
DEVICE_DATA = {
'x86_64-mlnx_msn2700-r0': {
'thermal': {
'minimum_table': {
"unk_trust": {"-127:30":13, "31:40":14 , "41:120":15},
"unk_untrust": {"-127:25":13, "26:30":14 , "31:35":15, "36:120":16}
},
"capability": {
"comex_amb": False
}
},
'fans': {
'drawer_num': 4,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn2740-r0': {
@ -41,20 +38,11 @@ DEVICE_DATA = {
'minimum_table': {
"unk_trust": {"-127:120":13},
"unk_untrust": {"-127:15":13, "16:25":14 , "26:30":15, "31:120":17},
},
"capability": {
"cpu_pack": False,
"comex_amb": False
}
},
'fans': {
'drawer_num': 4,
'drawer_type': 'real',
'fan_num_per_drawer': 1,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn2100-r0': {
@ -62,20 +50,11 @@ DEVICE_DATA = {
'minimum_table': {
"unk_trust": {"-127:40":12, "41:120":13},
"unk_untrust": {"-127:15":12, "16:25":13, "26:30":14, "31:35":15, "36:120":16}
},
"capability": {
"cpu_pack": False,
"comex_amb": False
}
},
'fans': {
'drawer_num': 1,
'drawer_type': 'virtual',
'fan_num_per_drawer': 4,
'support_fan_direction': True,
'hot_swappable': False
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': False,
'led_num': 2
}
},
'x86_64-mlnx_msn2410-r0': {
@ -83,20 +62,10 @@ DEVICE_DATA = {
'minimum_table': {
"unk_trust": {"-127:30":13, "31:40":14 , "41:120":15},
"unk_untrust": {"-127:25":13, "26:30":14 , "31:35":15, "36:120":16}
},
"capability": {
"comex_amb": False
}
},
'fans': {
'drawer_num': 4,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn2010-r0': {
@ -104,20 +73,11 @@ DEVICE_DATA = {
'minimum_table': {
"unk_trust": {"-127:120":12},
"unk_untrust": {"-127:15":12, "16:20":13 , "21:30":14, "31:35":15, "36:120":16}
},
"capability": {
"cpu_pack": False,
"comex_amb": False
}
},
'fans': {
'drawer_num': 1,
'drawer_type': 'virtual',
'fan_num_per_drawer': 4,
'support_fan_direction': True,
'hot_swappable': False
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': False,
'led_num': 2
}
},
'x86_64-mlnx_msn3700-r0': {
@ -126,19 +86,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:25":12, "26:40":13 , "41:120":14},
"unk_untrust": {"-127:15":12, "16:30":13 , "31:35":14, "36:40":15, "41:120":16},
}
},
'fans': {
'drawer_num': 6,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn3700c-r0': {
@ -147,19 +94,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:40":12, "41:120":13},
"unk_untrust": {"-127:10":12, "11:20":13 , "21:30":14, "31:35":15, "36:120":16},
}
},
'fans': {
'drawer_num': 4,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn3800-r0': {
@ -168,19 +102,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:30":12, "31:40":13 , "41:120":14},
"unk_untrust": {"-127:0":12, "1:10":13 , "11:15":14, "16:20":15, "21:35":16, "36:120":17},
}
},
'fans': {
'drawer_num': 3,
'drawer_type': 'real',
'fan_num_per_drawer': 1,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn4700-r0': {
@ -189,19 +110,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:35":14, "36:120":15},
"unk_untrust": {"-127:35":14, "36:120":15},
}
},
'fans': {
'drawer_num': 6,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn4410-r0': {
@ -210,19 +118,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:40":12, "41:120":13},
"unk_untrust": {"-127:10":12, "11:20":13, "21:30":14, "31:35":15, "36:120":16},
}
},
'fans': {
'drawer_num': 6,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn3420-r0': {
@ -231,19 +126,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:120":12},
"unk_untrust": {"-127:25":12, "26:35":13, "36:40":14, "41:120":16},
}
},
'fans': {
'drawer_num': 5,
'drawer_type': 'real',
'fan_num_per_drawer': 2,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn4600c-r0': {
@ -252,19 +134,6 @@ DEVICE_DATA = {
"unk_trust": {"-127:40":12, "41:120":13},
"unk_untrust": {"-127:5":12, "6:20":13, "21:30":14, "31:35":15, "36:40":16, "41:120":17},
}
},
'fans': {
'drawer_num': 3,
'drawer_type': 'real',
'fan_num_per_drawer': 1,
'support_fan_direction': True,
'hot_swappable': True
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
}
},
'x86_64-mlnx_msn4600-r0': {
@ -273,19 +142,113 @@ DEVICE_DATA = {
"unk_trust": {"-127:40": 12, "41:120": 13},
"unk_untrust": {"-127:5": 12, "6:20": 13, "21:30": 14, "31:35": 15, "36:40": 16, "41:120": 17},
}
}
},
'fans': {
'drawer_num': 3,
'drawer_type': 'real',
'fan_num_per_drawer': 1,
'support_fan_direction': True,
'hot_swappable': True
'x86_64-mlnx_msn4800-r0': {
'thermal': {
"capability": {
"comex_amb": False
}
},
'psus': {
'psu_num': 2,
'fan_num_per_psu': 1,
'hot_swappable': True,
'led_num': 1
'sfp': {
'max_port_per_line_card': 16
}
}
}
class DeviceDataManager:
@classmethod
@utils.read_only_cache()
def get_platform_name(cls):
return device_info.get_platform()
@classmethod
@utils.read_only_cache()
def get_fan_drawer_count(cls):
# Here we don't read from /run/hw-management/config/hotplug_fans because the value in it is not
# always correct.
return len(glob.glob('/run/hw-management/thermal/fan*_status')) if cls.is_fan_hotswapable() else 1
@classmethod
@utils.read_only_cache()
def get_fan_count(cls):
return len(glob.glob('/run/hw-management/thermal/fan*_speed_get'))
@classmethod
@utils.read_only_cache()
def is_fan_hotswapable(cls):
return utils.read_int_from_file('/run/hw-management/config/hotplug_fans') > 0
@classmethod
@utils.read_only_cache()
def get_psu_count(cls):
psu_count = utils.read_int_from_file('/run/hw-management/config/hotplug_psus')
# If psu_count == 0, the platform has fixed PSU
return psu_count if psu_count > 0 else len(glob.glob('/run/hw-management/config/psu*_i2c_addr'))
@classmethod
@utils.read_only_cache()
def is_psu_hotswapable(cls):
return utils.read_int_from_file('/run/hw-management/config/hotplug_psus') > 0
@classmethod
@utils.read_only_cache()
def get_sfp_count(cls):
return utils.read_int_from_file('/run/hw-management/config/sfp_counter')
@classmethod
def get_linecard_sfp_count(cls, lc_index):
return utils.read_int_from_file('/run/hw-management/lc{}/config/module_counter'.format(lc_index), log_func=None)
@classmethod
def get_gearbox_count(cls, sysfs_folder):
return utils.read_int_from_file(os.path.join(sysfs_folder, 'gearbox_counter'), log_func=None)
@classmethod
@utils.read_only_cache()
def get_cpu_thermal_count(cls):
return len(glob.glob('run/hw-management/thermal/cpu_core[!_]'))
@classmethod
@utils.read_only_cache()
def get_minimum_table(cls):
platform_data = DEVICE_DATA.get(cls.get_platform_name(), None)
if not platform_data:
return None
thermal_data = platform_data.get('thermal', None)
if not thermal_data:
return None
return thermal_data.get('minimum_table', None)
@classmethod
@utils.read_only_cache()
def get_thermal_capability(cls):
platform_data = DEVICE_DATA.get(cls.get_platform_name(), None)
if not platform_data:
return None
thermal_data = platform_data.get('thermal', None)
if not thermal_data:
return None
return thermal_data.get('capability', None)
@classmethod
@utils.read_only_cache()
def get_linecard_count(cls):
return utils.read_int_from_file('/run/hw-management/config/hotplug_linecards', log_func=None)
@classmethod
@utils.read_only_cache()
def get_linecard_max_port_count(cls):
platform_data = DEVICE_DATA.get(cls.get_platform_name(), None)
if not platform_data:
return 0
sfp_data = platform_data.get('sfp', None)
if not sfp_data:
return 0
return sfp_data.get('max_port_per_line_card', 0)

View File

@ -22,17 +22,16 @@
#
#############################################################################
import os
import time
import subprocess
from sonic_py_common.logger import Logger
from sonic_py_common.device_info import get_platform, get_path_to_platform_dir
try:
from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo
except ImportError as e:
raise ImportError (str(e) + "- required module not found")
from .utils import default_return
from .device_data import DeviceDataManager
from .utils import default_return, is_host
logger = Logger()
@ -41,30 +40,23 @@ logger = Logger()
# should this be moved to chass.py or here, which better?
#
EEPROM_SYMLINK = "/var/run/hw-management/eeprom/vpd_info"
platform_name = get_platform()
if 'simx' in platform_name:
platform_path = get_path_to_platform_dir()
platform_name = DeviceDataManager.get_platform_name()
if platform_name and 'simx' in platform_name:
if not os.path.exists(EEPROM_SYMLINK):
if is_host():
platform_path = os.path.join('/usr/share/sonic/device', platform_name)
else:
platform_path = '/usr/share/sonic/platform'
if not os.path.exists(os.path.dirname(EEPROM_SYMLINK)):
os.makedirs(os.path.dirname(EEPROM_SYMLINK))
subprocess.check_call(['/usr/bin/xxd', '-r', '-p', 'syseeprom.hex', EEPROM_SYMLINK], cwd=platform_path)
class Eeprom(eeprom_tlvinfo.TlvInfoDecoder):
RETRIES = 3
def __init__(self):
for attempt in range(self.RETRIES):
if not os.path.islink(EEPROM_SYMLINK):
time.sleep(1)
else:
break
if not os.path.exists(EEPROM_SYMLINK):
logger.log_error("Nowhere to read syseeprom from! No symlink or cache file found")
raise RuntimeError("No syseeprom symlink or cache file found")
logger.log_error("Nowhere to read syseeprom from! No symlink found")
raise RuntimeError("No syseeprom symlink found")
self.eeprom_path = EEPROM_SYMLINK
super(Eeprom, self).__init__(self.eeprom_path, 0, '', True)
@ -123,7 +115,6 @@ class Eeprom(eeprom_tlvinfo.TlvInfoDecoder):
"""
if self._eeprom_info_dict is None:
self._eeprom_info_dict = {}
# Try get from DB first
db_initialized = self._redis_hget('EEPROM_INFO|State', 'Initialized')
if db_initialized == '1':

View File

@ -27,11 +27,15 @@ import subprocess
try:
from sonic_platform_base.fan_base import FanBase
from .led import FanLed, ComponentFaultyIndicator
from .utils import read_int_from_file, read_str_from_file, write_file
from sonic_py_common.logger import Logger
from .led import ComponentFaultyIndicator
from . import utils
except ImportError as e:
raise ImportError (str(e) + "- required module not found")
# Global logger class instance
logger = Logger()
PWM_MAX = 255
FAN_PATH = "/var/run/hw-management/thermal/"
@ -42,110 +46,19 @@ FAN_DIR_VALUE_EXHAUST = 0
FAN_DIR_VALUE_INTAKE = 1
COOLING_STATE_PATH = "/var/run/hw-management/thermal/cooling_cur_state"
class Fan(FanBase):
"""Platform-specific Fan class"""
STATUS_LED_COLOR_ORANGE = "orange"
min_cooling_level = 2
class MlnxFan(FanBase):
MIN_VALID_COOLING_LEVEL = 1
MAX_VALID_COOLING_LEVEL = 10
# PSU fan speed vector
PSU_FAN_SPEED = ['0x3c', '0x3c', '0x3c', '0x3c', '0x3c',
'0x3c', '0x3c', '0x46', '0x50', '0x5a', '0x64']
def __init__(self, fan_index, fan_drawer, position, psu_fan = False, psu=None):
super(Fan, self).__init__()
# API index is starting from 0, Mellanox platform index is starting from 1
def __init__(self, fan_index, position):
super(MlnxFan, self).__init__()
self.index = fan_index + 1
self.fan_drawer = fan_drawer
self.position = position
self.is_psu_fan = psu_fan
self.psu = psu
if self.fan_drawer:
self.led = ComponentFaultyIndicator(self.fan_drawer.get_led())
elif self.is_psu_fan:
from .psu import Psu
self.led = ComponentFaultyIndicator(Psu.get_shared_led())
else:
self.led = FanLed(self.index)
if not self.is_psu_fan:
self.fan_speed_get_path = "fan{}_speed_get".format(self.index)
self.fan_speed_set_path = "fan{}_speed_set".format(self.index)
self.fan_max_speed_path = os.path.join(FAN_PATH, "fan{}_max".format(self.index))
self.fan_min_speed_path = os.path.join(FAN_PATH, "fan{}_min".format(self.index))
self._name = "fan{}".format(self.index)
else:
self.fan_speed_get_path = "psu{}_fan1_speed_get".format(self.index)
self.fan_presence_path = "psu{}_fan1_speed_get".format(self.index)
self._name = 'psu{}_fan{}'.format(self.index, 1)
self.fan_max_speed_path = os.path.join(FAN_PATH, "psu{}_fan_max".format(self.index))
self.fan_min_speed_path = os.path.join(FAN_PATH, "psu{}_fan_min".format(self.index))
self.psu_i2c_bus_path = os.path.join(CONFIG_PATH, 'psu{0}_i2c_bus'.format(self.index))
self.psu_i2c_addr_path = os.path.join(CONFIG_PATH, 'psu{0}_i2c_addr'.format(self.index))
self.psu_i2c_command_path = os.path.join(CONFIG_PATH, 'fan_command')
self.fan_status_path = "fan{}_fault".format(self.index)
self.fan_pwm_path = "pwm1"
def get_direction(self):
"""
Retrieves the fan's direction
Returns:
A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST
depending on fan direction
Notes:
What Mellanox calls forward:
Air flows from fans side to QSFP side, for example: MSN2700-CS2F
which means intake in community
What Mellanox calls reverse:
Air flow from QSFP side to fans side, for example: MSN2700-CS2R
which means exhaust in community
According to hw-mgmt:
1 stands for forward, in other words intake
0 stands for reverse, in other words exhaust
"""
if self.is_psu_fan:
return self.FAN_DIRECTION_NOT_APPLICABLE
else:
return self.fan_drawer.get_direction()
def get_name(self):
return self._name
def get_status(self):
"""
Retrieves the operational status of fan
Returns:
bool: True if fan is operating properly, False if not
"""
status = 0
if self.is_psu_fan:
status = 0
else:
status = read_int_from_file(os.path.join(FAN_PATH, self.fan_status_path), 1)
return status == 0
def get_presence(self):
"""
Retrieves the presence status of fan
Returns:
bool: True if fan is present, False if not
"""
if self.is_psu_fan:
return self.psu.get_presence() and self.psu.get_powergood_status() and os.path.exists(os.path.join(FAN_PATH, self.fan_presence_path))
else:
return self.fan_drawer.get_presence()
def get_speed(self):
"""
Retrieves the speed of fan
@ -154,9 +67,9 @@ class Fan(FanBase):
int: percentage of the max fan speed
"""
speed = 0
speed_in_rpm = read_int_from_file(os.path.join(FAN_PATH, self.fan_speed_get_path))
speed_in_rpm = utils.read_int_from_file(self.fan_speed_get_path)
max_speed_in_rpm = read_int_from_file(self.fan_max_speed_path)
max_speed_in_rpm = utils.read_int_from_file(self.fan_max_speed_path)
if max_speed_in_rpm == 0:
return speed_in_rpm
@ -166,72 +79,6 @@ class Fan(FanBase):
return speed
def get_target_speed(self):
"""
Retrieves the expected speed of fan
Returns:
int: percentage of the max fan speed
"""
if self.is_psu_fan:
try:
# Get PSU fan target speed according to current system cooling level
cooling_level = self.get_cooling_level()
return int(self.PSU_FAN_SPEED[cooling_level], 16)
except Exception:
return self.get_speed()
pwm = read_int_from_file(os.path.join(FAN_PATH, self.fan_speed_set_path))
return int(round(pwm*100.0/PWM_MAX))
def set_speed(self, speed):
"""
Set fan speed to expected value
Args:
speed: An integer, the percentage of full fan speed to set fan to,
in the range 0 (off) to 100 (full speed)
Returns:
bool: True if set success, False if fail.
"""
status = True
if self.is_psu_fan:
if not self.get_presence():
return False
from .thermal import logger
try:
bus = read_str_from_file(self.psu_i2c_bus_path, raise_exception=True)
addr = read_str_from_file(self.psu_i2c_addr_path, raise_exception=True)
command = read_str_from_file(self.psu_i2c_command_path, raise_exception=True)
speed = Fan.PSU_FAN_SPEED[int(speed // 10)]
command = "i2cset -f -y {0} {1} {2} {3} wp".format(bus, addr, command, speed)
subprocess.check_call(command, shell = True, universal_newlines=True)
return True
except subprocess.CalledProcessError as ce:
logger.log_error('Failed to call command {}, return code={}, command output={}'.format(ce.cmd, ce.returncode, ce.output))
return False
except Exception as e:
logger.log_error('Failed to set PSU FAN speed - {}'.format(e))
return False
try:
cooling_level = int(speed // 10)
if cooling_level < self.min_cooling_level:
cooling_level = self.min_cooling_level
speed = self.min_cooling_level * 10
self.set_cooling_level(cooling_level, cooling_level)
pwm = int(round(PWM_MAX*speed/100.0))
write_file(os.path.join(FAN_PATH, self.fan_speed_set_path), pwm, raise_exception=True)
except (ValueError, IOError):
status = False
return status
def set_status_led(self, color):
"""
Set led to expected color
@ -245,7 +92,6 @@ class Fan(FanBase):
"""
return self.led.set_status(color)
def get_status_led(self):
"""
Gets the state of the fan status LED
@ -255,7 +101,6 @@ class Fan(FanBase):
"""
return self.led.get_status()
def get_speed_tolerance(self):
"""
Retrieves the speed tolerance of the fan
@ -289,9 +134,6 @@ class Fan(FanBase):
Change cooling level. The input level should be an integer value [1, 10].
1 means 10%, 2 means 20%, 10 means 100%.
"""
if not isinstance(level, int):
raise RuntimeError("Failed to set cooling level, input parameter must be integer")
if level < cls.MIN_VALID_COOLING_LEVEL or level > cls.MAX_VALID_COOLING_LEVEL:
raise RuntimeError("Failed to set cooling level, level value must be in range [{}, {}], got {}".format(
cls.MIN_VALID_COOLING_LEVEL,
@ -303,16 +145,214 @@ class Fan(FanBase):
# Reset FAN cooling level vector. According to low level team,
# if we need set cooling level to X, we need first write a (10+X)
# to cooling_cur_state file to reset the cooling level vector.
write_file(COOLING_STATE_PATH, level + 10, raise_exception=True)
utils.write_file(COOLING_STATE_PATH, level + 10, raise_exception=True)
# We need set cooling level after resetting the cooling level vector
write_file(COOLING_STATE_PATH, cur_state, raise_exception=True)
utils.write_file(COOLING_STATE_PATH, cur_state, raise_exception=True)
except (ValueError, IOError) as e:
raise RuntimeError("Failed to set cooling level - {}".format(e))
@classmethod
def get_cooling_level(cls):
try:
return read_int_from_file(COOLING_STATE_PATH, raise_exception=True)
return utils.read_int_from_file(COOLING_STATE_PATH, raise_exception=True)
except (ValueError, IOError) as e:
raise RuntimeError("Failed to get cooling level - {}".format(e))
class PsuFan(MlnxFan):
# PSU fan speed vector
PSU_FAN_SPEED = ['0x3c', '0x3c', '0x3c', '0x3c', '0x3c',
'0x3c', '0x3c', '0x46', '0x50', '0x5a', '0x64']
def __init__(self, fan_index, position, psu):
super(PsuFan, self).__init__(fan_index, position)
self._name = 'psu{}_fan{}'.format(self.index, position)
self.psu = psu
from .psu import Psu
self.led = ComponentFaultyIndicator(Psu.get_shared_led())
self.fan_speed_get_path = os.path.join(FAN_PATH, "psu{}_fan1_speed_get".format(self.index))
self.fan_presence_path = os.path.join(FAN_PATH, "psu{}_fan1_speed_get".format(self.index))
self.fan_max_speed_path = os.path.join(FAN_PATH, "psu{}_fan_max".format(self.index))
self.fan_min_speed_path = os.path.join(FAN_PATH, "psu{}_fan_min".format(self.index))
self.psu_i2c_bus_path = os.path.join(CONFIG_PATH, 'psu{0}_i2c_bus'.format(self.index))
self.psu_i2c_addr_path = os.path.join(CONFIG_PATH, 'psu{0}_i2c_addr'.format(self.index))
self.psu_i2c_command_path = os.path.join(CONFIG_PATH, 'fan_command')
def get_direction(self):
"""
Retrieves the fan's direction
Returns:
A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST
depending on fan direction
Notes:
What Mellanox calls forward:
Air flows from fans side to QSFP side, for example: MSN2700-CS2F
which means intake in community
What Mellanox calls reverse:
Air flow from QSFP side to fans side, for example: MSN2700-CS2R
which means exhaust in community
According to hw-mgmt:
1 stands for forward, in other words intake
0 stands for reverse, in other words exhaust
"""
return self.FAN_DIRECTION_NOT_APPLICABLE
def get_status(self):
"""
Retrieves the operational status of fan
Returns:
bool: True if fan is operating properly, False if not
"""
return True
def get_presence(self):
"""
Retrieves the presence status of fan
Returns:
bool: True if fan is present, False if not
"""
return self.psu.get_presence() and self.psu.get_powergood_status() and os.path.exists(self.fan_presence_path)
def get_target_speed(self):
"""
Retrieves the expected speed of fan
Returns:
int: percentage of the max fan speed
"""
try:
# Get PSU fan target speed according to current system cooling level
cooling_level = self.get_cooling_level()
return int(self.PSU_FAN_SPEED[cooling_level], 16)
except Exception:
return self.get_speed()
def set_speed(self, speed):
"""
Set fan speed to expected value
Args:
speed: An integer, the percentage of full fan speed to set fan to,
in the range 0 (off) to 100 (full speed)
Returns:
bool: True if set success, False if fail.
"""
if not self.get_presence():
return False
try:
bus = utils.read_str_from_file(self.psu_i2c_bus_path, raise_exception=True)
addr = utils.read_str_from_file(self.psu_i2c_addr_path, raise_exception=True)
command = utils.read_str_from_file(self.psu_i2c_command_path, raise_exception=True)
speed = self.PSU_FAN_SPEED[int(speed // 10)]
command = "i2cset -f -y {0} {1} {2} {3} wp".format(bus, addr, command, speed)
subprocess.check_call(command, shell = True, universal_newlines=True)
return True
except subprocess.CalledProcessError as ce:
logger.log_error('Failed to call command {}, return code={}, command output={}'.format(ce.cmd, ce.returncode, ce.output))
return False
except Exception as e:
logger.log_error('Failed to set PSU FAN speed - {}'.format(e))
return False
class Fan(MlnxFan):
"""Platform-specific Fan class"""
min_cooling_level = 2
def __init__(self, fan_index, fan_drawer, position):
super(Fan, self).__init__(fan_index, position)
self.fan_drawer = fan_drawer
self.led = ComponentFaultyIndicator(self.fan_drawer.get_led())
self._name = "fan{}".format(self.index)
self.fan_speed_get_path = os.path.join(FAN_PATH, "fan{}_speed_get".format(self.index))
self.fan_speed_set_path = os.path.join(FAN_PATH, "fan{}_speed_set".format(self.index))
self.fan_max_speed_path = os.path.join(FAN_PATH, "fan{}_max".format(self.index))
self.fan_min_speed_path = os.path.join(FAN_PATH, "fan{}_min".format(self.index))
self.fan_status_path = os.path.join(FAN_PATH, "fan{}_fault".format(self.index))
def get_direction(self):
"""
Retrieves the fan's direction
Returns:
A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST
depending on fan direction
Notes:
What Mellanox calls forward:
Air flows from fans side to QSFP side, for example: MSN2700-CS2F
which means intake in community
What Mellanox calls reverse:
Air flow from QSFP side to fans side, for example: MSN2700-CS2R
which means exhaust in community
According to hw-mgmt:
1 stands for forward, in other words intake
0 stands for reverse, in other words exhaust
"""
return self.fan_drawer.get_direction()
def get_status(self):
"""
Retrieves the operational status of fan
Returns:
bool: True if fan is operating properly, False if not
"""
return utils.read_int_from_file(self.fan_status_path, 1) == 0
def get_presence(self):
"""
Retrieves the presence status of fan
Returns:
bool: True if fan is present, False if not
"""
return self.fan_drawer.get_presence()
def get_target_speed(self):
"""
Retrieves the expected speed of fan
Returns:
int: percentage of the max fan speed
"""
pwm = utils.read_int_from_file(self.fan_speed_set_path)
return int(round(pwm*100.0/PWM_MAX))
def set_speed(self, speed):
"""
Set fan speed to expected value
Args:
speed: An integer, the percentage of full fan speed to set fan to,
in the range 0 (off) to 100 (full speed)
Returns:
bool: True if set success, False if fail.
"""
status = True
try:
cooling_level = int(speed // 10)
if cooling_level < self.min_cooling_level:
cooling_level = self.min_cooling_level
speed = self.min_cooling_level * 10
self.set_cooling_level(cooling_level, cooling_level)
pwm = int(PWM_MAX*speed/100.0)
utils.write_file(self.fan_speed_set_path, pwm, raise_exception=True)
except (ValueError, IOError):
status = False
return status

View File

@ -27,18 +27,21 @@ import os
try:
from sonic_platform_base.fan_drawer_base import FanDrawerBase
from sonic_platform_base.fan_base import FanBase
from sonic_py_common.logger import Logger
from .led import FanLed, SharedLed
from .utils import read_int_from_file
from . import utils
except ImportError as e:
raise ImportError (str(e) + "- required module not found")
# Global logger class instance
logger = Logger()
class MellanoxFanDrawer(FanDrawerBase):
def __init__(self, index, fan_data):
def __init__(self, index):
from .fan import FAN_PATH
super(MellanoxFanDrawer, self).__init__()
self._index = index + 1
self._fan_data = fan_data
self._presence_path = os.path.join(FAN_PATH, 'fan{}_status'.format(self._index))
self._led = None
@ -49,33 +52,25 @@ class MellanoxFanDrawer(FanDrawerBase):
return self._led
def get_presence(self):
if not self._fan_data['hot_swappable']:
return True
status = 0
try:
with open(self._presence_path, 'r') as presence_status:
status = int(presence_status.read())
except (ValueError, IOError) as e:
status = 0
return status == 1
return utils.read_int_from_file(self._presence_path) == 1
def get_direction(self):
if not self._fan_data['support_fan_direction'] or not self.get_presence():
if not self.get_presence():
return FanBase.FAN_DIRECTION_NOT_APPLICABLE
try:
from .fan import FAN_DIR, FAN_DIR_VALUE_INTAKE, FAN_DIR_VALUE_EXHAUST
fan_dir = read_int_from_file(FAN_DIR.format(self._index), raise_exception=True)
fan_dir = utils.read_int_from_file(FAN_DIR.format(self._index), raise_exception=True)
if fan_dir == FAN_DIR_VALUE_INTAKE:
return FanBase.FAN_DIRECTION_INTAKE
elif fan_dir == FAN_DIR_VALUE_EXHAUST:
return FanBase.FAN_DIRECTION_EXHAUST
else:
raise RuntimeError("Got wrong value {} for fan direction {}".format(fan_dir, self._index))
logger.log_error("Got wrong value {} for fan direction {}".format(fan_dir, self._index))
return FanBase.FAN_DIRECTION_NOT_APPLICABLE
except (ValueError, IOError) as e:
raise RuntimeError("Failed to read fan direction status to {}".format(repr(e)))
logger.log_error("Failed to read fan direction status to {}".format(repr(e)))
return FanBase.FAN_DIRECTION_NOT_APPLICABLE
def set_status_led(self, color):
"""
@ -113,12 +108,12 @@ class MellanoxFanDrawer(FanDrawerBase):
Returns:
bool: True if it is replaceable.
"""
return self._fan_data['hot_swappable']
return True
class RealDrawer(MellanoxFanDrawer):
def __init__(self, index, fan_data):
super(RealDrawer, self).__init__(index, fan_data)
def __init__(self, index):
super(RealDrawer, self).__init__(index)
self._name = 'drawer{}'.format(self._index)
self._led = SharedLed(FanLed(self._index))
@ -127,9 +122,15 @@ class RealDrawer(MellanoxFanDrawer):
class VirtualDrawer(MellanoxFanDrawer):
def __init__(self, index, fan_data):
super(VirtualDrawer, self).__init__(index, fan_data)
def __init__(self, index):
super(VirtualDrawer, self).__init__(index)
self._led = SharedLed(FanLed(None))
def get_name(self):
return 'N/A'
def get_presence(self):
return True
def is_replaceable(self):
return False

View File

@ -15,7 +15,11 @@
# limitations under the License.
#
import os
from sonic_py_common.logger import Logger
from . import utils
logger = Logger()
class Led(object):
STATUS_LED_COLOR_GREEN = 'green'
@ -26,7 +30,7 @@ class Led(object):
STATUS_LED_COLOR_ORANGE_BLINK = 'orange_blink'
STATUS_LED_COLOR_OFF = 'off'
LED_ON = '1'
LED_ON = '255'
LED_OFF = '0'
LED_BLINK = '50'
@ -42,11 +46,10 @@ class Led(object):
self._stop_blink(led_cap_list)
blink_pos = color.find('blink')
if blink_pos != -1:
return self._set_status_blink(color, blink_pos, led_cap_list)
return self._set_status_blink(color, led_cap_list)
if color == Led.STATUS_LED_COLOR_GREEN:
with open(self.get_green_led_path(), 'w') as led:
led.write(Led.LED_ON)
utils.write_file(self.get_green_led_path(), Led.LED_ON)
status = True
elif color == Led.STATUS_LED_COLOR_RED:
# Some led don't support red led but support orange led, in this case we set led to orange
@ -57,19 +60,15 @@ class Led(object):
else:
return False
with open(led_path, 'w') as led:
led.write(Led.LED_ON)
utils.write_file(led_path, Led.LED_ON)
status = True
elif color == Led.STATUS_LED_COLOR_OFF:
if Led.STATUS_LED_COLOR_GREEN in led_cap_list:
with open(self.get_green_led_path(), 'w') as led:
led.write(Led.LED_OFF)
utils.write_file(self.get_green_led_path(), Led.LED_OFF)
if Led.STATUS_LED_COLOR_RED in led_cap_list:
with open(self.get_red_led_path(), 'w') as led:
led.write(Led.LED_OFF)
utils.write_file(self.get_red_led_path(), Led.LED_OFF)
if Led.STATUS_LED_COLOR_ORANGE in led_cap_list:
with open(self.get_orange_led_path(), 'w') as led:
led.write(Led.LED_OFF)
utils.write_file(self.get_orange_led_path(), Led.LED_OFF)
status = True
else:
@ -79,7 +78,7 @@ class Led(object):
return status
def _set_status_blink(self, color, blink_pos, led_cap_list):
def _set_status_blink(self, color, led_cap_list):
if color not in led_cap_list:
if color == Led.STATUS_LED_COLOR_RED_BLINK and Led.STATUS_LED_COLOR_ORANGE_BLINK in led_cap_list:
color = Led.STATUS_LED_COLOR_ORANGE_BLINK
@ -89,16 +88,14 @@ class Led(object):
return False
if Led.STATUS_LED_COLOR_GREEN_BLINK == color:
self._set_led_blink_status(self.get_green_led_delay_on_path(), self.get_green_led_delay_off_path(), Led.LED_BLINK)
return self._set_led_blink_status(self.get_green_led_delay_on_path(), self.get_green_led_delay_off_path(), Led.LED_BLINK)
elif Led.STATUS_LED_COLOR_RED_BLINK == color:
self._set_led_blink_status(self.get_red_led_delay_on_path(), self.get_red_led_delay_off_path(), Led.LED_BLINK)
return self._set_led_blink_status(self.get_red_led_delay_on_path(), self.get_red_led_delay_off_path(), Led.LED_BLINK)
elif Led.STATUS_LED_COLOR_ORANGE_BLINK == color:
self._set_led_blink_status(self.get_orange_led_delay_on_path(), self.get_orange_led_delay_off_path(), Led.LED_BLINK)
return self._set_led_blink_status(self.get_orange_led_delay_on_path(), self.get_orange_led_delay_off_path(), Led.LED_BLINK)
else:
return False
return True
def _stop_blink(self, led_cap_list):
try:
if Led.STATUS_LED_COLOR_GREEN_BLINK in led_cap_list:
@ -111,10 +108,9 @@ class Led(object):
return
def _set_led_blink_status(self, delay_on_file, delay_off_file, value):
with open(delay_on_file, 'w') as led:
led.write(value)
with open(delay_off_file, 'w') as led:
led.write(value)
utils.write_file(delay_on_file, value)
utils.write_file(delay_off_file, value)
return True
def get_status(self):
led_cap_list = self.get_capability()
@ -126,17 +122,14 @@ class Led(object):
if blink_status is not None:
return blink_status
with open(self.get_green_led_path(), 'r') as led:
if Led.LED_OFF != led.read().rstrip('\n'):
if utils.read_str_from_file(self.get_green_led_path()) != Led.LED_OFF:
return Led.STATUS_LED_COLOR_GREEN
if Led.STATUS_LED_COLOR_RED in led_cap_list:
with open(self.get_red_led_path(), 'r') as led:
if Led.LED_OFF != led.read().rstrip('\n'):
if utils.read_str_from_file(self.get_red_led_path()) != Led.LED_OFF:
return Led.STATUS_LED_COLOR_RED
if Led.STATUS_LED_COLOR_ORANGE in led_cap_list:
with open(self.get_orange_led_path(), 'r') as led:
if Led.LED_OFF != led.read().rstrip('\n'):
if utils.read_str_from_file(self.get_orange_led_path()) != Led.LED_OFF:
return Led.STATUS_LED_COLOR_RED
except (ValueError, IOError) as e:
raise RuntimeError("Failed to read led status due to {}".format(repr(e)))
@ -148,6 +141,7 @@ class Led(object):
if Led.STATUS_LED_COLOR_GREEN_BLINK in led_cap_list:
if self._is_led_blinking(self.get_green_led_delay_on_path(), self.get_green_led_delay_off_path()):
return Led.STATUS_LED_COLOR_GREEN_BLINK
if Led.STATUS_LED_COLOR_RED_BLINK in led_cap_list:
if self._is_led_blinking(self.get_red_led_delay_on_path(), self.get_red_led_delay_off_path()):
return Led.STATUS_LED_COLOR_RED_BLINK
@ -160,126 +154,73 @@ class Led(object):
return None
def _is_led_blinking(self, delay_on_file, delay_off_file):
with open(delay_on_file, 'r') as led:
delay_on = led.read().rstrip('\n')
with open(delay_off_file, 'r') as led:
delay_off = led.read().rstrip('\n')
delay_on = utils.read_str_from_file(delay_on_file, default=Led.LED_OFF, log_func=None)
delay_off = utils.read_str_from_file(delay_off_file, default=Led.LED_OFF, log_func=None)
return delay_on != Led.LED_OFF and delay_off != Led.LED_OFF
def get_capability(self):
cap_list = None
try:
with open(self.get_led_cap_path(), 'r') as led_cap:
caps = led_cap.read()
cap_list = set(caps.split())
except (ValueError, IOError):
pass
return cap_list
caps = utils.read_str_from_file(self.get_led_cap_path())
return set(caps.split())
def get_green_led_path(self):
pass
return os.path.join(Led.LED_PATH, 'led_{}_green'.format(self._led_id))
def get_green_led_delay_off_path(self):
return '{}_delay_off'.format(self.get_green_led_path())
return os.path.join(Led.LED_PATH, 'led_{}_green_delay_off'.format(self._led_id))
def get_green_led_delay_on_path(self):
return '{}_delay_on'.format(self.get_green_led_path())
return os.path.join(Led.LED_PATH, 'led_{}_green_delay_on'.format(self._led_id))
def get_green_led_trigger(self):
return os.path.join(Led.LED_PATH, 'led_{}_green_trigger'.format(self._led_id))
def get_red_led_path(self):
pass
return os.path.join(Led.LED_PATH, 'led_{}_red'.format(self._led_id))
def get_red_led_delay_off_path(self):
return '{}_delay_off'.format(self.get_red_led_path())
return os.path.join(Led.LED_PATH, 'led_{}_red_delay_off'.format(self._led_id))
def get_red_led_delay_on_path(self):
return '{}_delay_on'.format(self.get_red_led_path())
return os.path.join(Led.LED_PATH, 'led_{}_red_delay_on'.format(self._led_id))
def get_red_led_trigger(self):
return os.path.join(Led.LED_PATH, 'led_{}_red_trigger'.format(self._led_id))
def get_orange_led_path(self):
pass
return os.path.join(Led.LED_PATH, 'led_{}_orange'.format(self._led_id))
def get_orange_led_delay_off_path(self):
return '{}_delay_off'.format(self.get_orange_led_path())
return os.path.join(Led.LED_PATH, 'led_{}_orange_delay_off'.format(self._led_id))
def get_orange_led_delay_on_path(self):
return '{}_delay_on'.format(self.get_orange_led_path())
return os.path.join(Led.LED_PATH, 'led_{}_orange_delay_on'.format(self._led_id))
def get_orange_led_trigger(self):
return os.path.join(Led.LED_PATH, 'led_{}_orange_trigger'.format(self._led_id))
def get_led_cap_path(self):
pass
return os.path.join(Led.LED_PATH, 'led_{}_capability'.format(self._led_id))
class FanLed(Led):
LED_PATH = "/var/run/hw-management/led/"
def __init__(self, index):
if index is not None:
self._green_led_path = os.path.join(Led.LED_PATH, "led_fan{}_green".format(index))
self._red_led_path = os.path.join(Led.LED_PATH, "led_fan{}_red".format(index))
self._orange_led_path = os.path.join(Led.LED_PATH, "led_fan{}_orange".format(index))
self._led_cap_path = os.path.join(Led.LED_PATH, "led_fan{}_capability".format(index))
self._led_id = 'fan{}'.format(index)
else:
self._green_led_path = os.path.join(Led.LED_PATH, "led_fan_green")
self._red_led_path = os.path.join(Led.LED_PATH, "led_fan_red")
self._orange_led_path = os.path.join(Led.LED_PATH, "led_fan_orange")
self._led_cap_path = os.path.join(Led.LED_PATH, "led_fan_capability")
def get_green_led_path(self):
return self._green_led_path
def get_red_led_path(self):
return self._red_led_path
def get_orange_led_path(self):
return self._orange_led_path
def get_led_cap_path(self):
return self._led_cap_path
self._led_id = 'fan'
class PsuLed(Led):
def __init__(self, index):
if index is not None:
self._green_led_path = os.path.join(Led.LED_PATH, "led_psu{}_green".format(index))
self._red_led_path = os.path.join(Led.LED_PATH, "led_psu{}_red".format(index))
self._orange_led_path = os.path.join(Led.LED_PATH, "led_psu{}_orange".format(index))
self._led_cap_path = os.path.join(Led.LED_PATH, "led_psu{}_capability".format(index))
self._led_id = 'psu{}'.format(index)
else:
self._green_led_path = os.path.join(Led.LED_PATH, "led_psu_green")
self._red_led_path = os.path.join(Led.LED_PATH, "led_psu_red")
self._orange_led_path = os.path.join(Led.LED_PATH, "led_psu_orange")
self._led_cap_path = os.path.join(Led.LED_PATH, "led_psu_capability")
def get_green_led_path(self):
return self._green_led_path
def get_red_led_path(self):
return self._red_led_path
def get_orange_led_path(self):
return self._orange_led_path
def get_led_cap_path(self):
return self._led_cap_path
self._led_id = 'psu'
class SystemLed(Led):
def __init__(self):
self._green_led_path = os.path.join(Led.LED_PATH, "led_status_green")
self._red_led_path = os.path.join(Led.LED_PATH, "led_status_red")
self._orange_led_path = os.path.join(Led.LED_PATH, "led_status_orange")
self._led_cap_path = os.path.join(Led.LED_PATH, "led_status_capability")
def get_green_led_path(self):
return self._green_led_path
def get_red_led_path(self):
return self._red_led_path
def get_orange_led_path(self):
return self._orange_led_path
def get_led_cap_path(self):
return self._led_cap_path
self._led_id = 'status'
class SharedLed(object):

View File

@ -0,0 +1,249 @@
#
# 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 redis
import threading
from sonic_platform_base.module_base import ModuleBase
from sonic_py_common.logger import Logger
from . import utils
from .device_data import DeviceDataManager
from .vpd_parser import VpdParser
# Global logger class instance
logger = Logger()
class Module(ModuleBase):
STATE_ACTIVATED = 1
STATE_DEACTIVATED = 0
STATE_DB = 6
STATE_MODULAR_CHASSIS_SLOT_TABLE = 'MODULAR_CHASSIS_SLOT|{}'
FIELD_SEQ_NO = 'seq_no'
redis_client = redis.Redis(db = STATE_DB)
def __init__(self, slot_id):
super(Module, self).__init__()
self.slot_id = slot_id
self.seq_no = 0
self.current_state = Module.STATE_DEACTIVATED
self.lock = threading.Lock()
self.sfp_initialized_count = 0
self.sfp_count = 0
self.vpd_parser = VpdParser('/run/hw-management/lc{}/eeprom/vpd_parsed')
def get_name(self):
return 'LINE-CARD{}'.format(self.slot_id)
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
return self.vpd_parser.get_model()
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
return self.vpd_parser.get_serial()
def get_revision(self):
"""
Retrieves the hardware revision of the device
Returns:
string: Revision value of device
"""
return self.vpd_parser.get_revision()
def get_type(self):
return ModuleBase.MODULE_TYPE_LINE
def get_slot(self):
return self.slot_id
def get_presence(self):
return utils.read_int_from_file('/run/hw-management/system/lc{}_present'.format(self.slot_id)) == 1
def get_position_in_parent(self):
return self.slot_id
def is_replaceable(self):
return True
def get_oper_status(self): # TODO: read from DB?
if utils.read_int_from_file('/run/hw-management/system/lc{}_active'.format(self.slot_id)) == 1:
return ModuleBase.MODULE_STATUS_ONLINE
elif utils.read_int_from_file('/run/hw-management/system/lc{}_present'.format(self.slot_id)) == 1:
return ModuleBase.MODULE_STATUS_PRESENT
elif utils.read_int_from_file('/run/hw-management/system/lc{}_present'.format(self.slot_id)) == 0:
return ModuleBase.MODULE_STATUS_EMPTY
else:
return ModuleBase.MODULE_STATUS_FAULT
def _check_state(self):
"""Check Module status change:
1. If status sysfs file value has been changed TODO: read from DB?
2. If sequence NO has been changed which means line card has been removed and inserted again.
"""
seq_no = self._get_seq_no()
state = utils.read_int_from_file('/run/hw-management/system/lc{}_powered'.format(self.slot_id), log_func=None)
if state != self.current_state:
self._re_init()
elif seq_no != self.seq_no:
if state == Module.STATE_ACTIVATED: # LC has been replaced, need re-initialize
self._re_init()
self.current_state = state
self.seq_no = seq_no
def _get_seq_no(self):
try:
seq_no = Module.redis_client.hget(Module.STATE_MODULAR_CHASSIS_SLOT_TABLE.format(self.slot_id), Module.FIELD_SEQ_NO)
seq_no = seq_no.decode().strip()
except Exception as e:
seq_no = 0
return seq_no
def _re_init(self):
self._thermal_list = []
self._sfp_list = []
self._sfp_count = 0
##############################################
# THERMAL methods
##############################################
def initialize_thermals(self):
self._check_state()
if self.current_state == Module.STATE_ACTIVATED and not self._thermal_list:
from .thermal import initialize_linecard_thermals
self._thermal_list = initialize_linecard_thermals(self.get_name(), self.slot_id) # TODO: add presence_cb?
def get_num_thermals(self):
"""
Retrieves the number of thermals available on this module
Returns:
An integer, the number of thermals available on this module
"""
return DeviceDataManager.get_gearbox_count('/run/hw-management/lc{}/config'.format(self.slot_id))
def get_all_thermals(self):
"""
Retrieves all thermals available on this module
Returns:
A list of objects derived from ThermalBase representing all thermals
available on this module
"""
with self.lock:
self.initialize_thermals()
return self._thermal_list
def get_thermal(self, index):
"""
Retrieves thermal unit represented by (0-based) index <index>
Args:
index: An integer, the index (0-based) of the thermal to
retrieve
Returns:
An object dervied from ThermalBase representing the specified thermal
"""
with self.lock:
self.initialize_thermals()
return super(Module, self).get_thermal(index)
##############################################
# SFP methods
##############################################
def _create_sfp_object(self, index):
from .sfp import SFP
return SFP(index, slot_id=self.slot_id, linecard_port_count=self.sfp_count, lc_name=self.get_name())
def initialize_single_sfp(self, index):
self._check_state()
if self.current_state == Module.STATE_ACTIVATED:
sfp_count = self.get_num_sfps()
if index < sfp_count:
if not self._sfp_list:
self._sfp_list = [None] * sfp_count
if not self._sfp_list[index]:
self._sfp_list[index] = self._create_sfp_object(index)
self.sfp_initialized_count += 1
def initialize_sfps(self):
self._check_state()
if self.current_state == Module.STATE_ACTIVATED:
if not self._sfp_list:
sfp_count = self.get_num_sfps()
for index in range(sfp_count):
self._sfp_list.append(self._create_sfp_object(index))
self.sfp_initialized_count = sfp_count
elif self.sfp_initialized_count != len(self._sfp_list):
for index in range(len(self._sfp_list)):
if self._sfp_list[index] is None:
self._sfp_list[index] = self._create_sfp_object(index)
self.sfp_initialized_count = len(self._sfp_list)
def get_num_sfps(self):
"""
Retrieves the number of sfps available on this module
Returns:
An integer, the number of sfps available on this module
"""
if self.sfp_count == 0:
self.sfp_count = DeviceDataManager.get_linecard_sfp_count(self.slot_id)
return self.sfp_count
def get_all_sfps(self):
"""
Retrieves all sfps available on this module
Returns:
A list of objects derived from PsuBase representing all sfps
available on this module
"""
with self.lock:
self.initialize_sfps()
return self._sfp_list
def get_sfp(self, index):
"""
Retrieves sfp represented by (0-based) index <index>
Args:
index: An integer, the index (0-based) of the sfp to retrieve
Returns:
An object dervied from SfpBase representing the specified sfp
"""
with self.lock:
self.initialize_single_sfp(index)
return super(Module, self).get_sfp(index)

View File

@ -22,23 +22,15 @@
try:
from sonic_platform_base.platform_base import PlatformBase
from sonic_platform.chassis import Chassis
from sonic_py_common.device_info import get_platform
from . import utils
from .chassis import Chassis, ModularChassis
from .device_data import DeviceDataManager
except ImportError as e:
raise ImportError(str(e) + "- required module not found")
class Platform(PlatformBase):
def __init__(self):
PlatformBase.__init__(self)
if DeviceDataManager.get_linecard_count() == 0:
self._chassis = Chassis()
self._chassis.initialize_eeprom()
platform_name = get_platform()
if "simx" not in platform_name:
self._chassis.initialize_psu()
if utils.is_host():
self._chassis.initialize_components()
self._chassis.initizalize_system_led()
else:
self._chassis.initialize_fan()
self._chassis.initialize_thermals()
self._chassis = ModularChassis()

View File

@ -23,12 +23,12 @@
#############################################################################
try:
import os.path
import os
from sonic_platform_base.psu_base import PsuBase
from sonic_py_common.logger import Logger
from sonic_platform.fan import Fan
from .led import PsuLed, SharedLed, ComponentFaultyIndicator
from .device_data import DEVICE_DATA
from . import utils
from .vpd_parser import VpdParser
except ImportError as e:
raise ImportError (str(e) + "- required module not found")
@ -36,210 +36,28 @@ except ImportError as e:
# Global logger class instance
logger = Logger()
psu_list = []
PSU_PATH = '/var/run/hw-management/'
PSU_CURRENT = "current"
PSU_VOLTAGE = "voltage"
PSU_POWER = "power"
PSU_VPD = "vpd"
SN_VPD_FIELD = "SN_VPD_FIELD"
PN_VPD_FIELD = "PN_VPD_FIELD"
REV_VPD_FIELD = "REV_VPD_FIELD"
# in most platforms the file psuX_curr, psuX_volt and psuX_power contain current, voltage and power data respectively.
# but there are exceptions which will be handled by the following dictionary
platform_dict_psu = {'x86_64-mlnx_msn3420-r0': 1, 'x86_64-mlnx_msn3700-r0': 1, 'x86_64-mlnx_msn3700c-r0': 1,
'x86_64-mlnx_msn3800-r0': 1, 'x86_64-mlnx_msn4600-r0': 1, 'x86_64-mlnx_msn4600c-r0': 1,
'x86_64-mlnx_msn4700-r0': 1, 'x86_64-mlnx_msn4410-r0': 1, 'x86_64-mlnx_msn2010-r0' : 2,
'x86_64-mlnx_msn2100-r0': 2}
psu_profile_list = [
# default filename convention
{
PSU_CURRENT : "power/psu{}_curr",
PSU_VOLTAGE : "power/psu{}_volt",
PSU_POWER : "power/psu{}_power",
PSU_VPD : "eeprom/psu{}_vpd"
},
# for 3420, 3700, 3700c, 3800, 4600c, 4700
{
PSU_CURRENT : "power/psu{}_curr",
PSU_VOLTAGE : "power/psu{}_volt_out2",
PSU_POWER : "power/psu{}_power",
PSU_VPD : "eeprom/psu{}_vpd"
},
# for fixed platforms 2100, 2010
{
PSU_CURRENT : "power/psu{}_curr",
PSU_VOLTAGE : "power/psu{}_volt_out2",
PSU_POWER : "power/psu{}_power",
PSU_VPD : None
}
]
class Psu(PsuBase):
"""Platform-specific Psu class"""
shared_led = None
def __init__(self, psu_index, platform):
global psu_list
PsuBase.__init__(self)
# PSU is 1-based on Mellanox platform
class FixedPsu(PsuBase):
def __init__(self, psu_index):
super(FixedPsu, self).__init__()
self.index = psu_index + 1
psu_list.append(self.index)
self.psu_path = "/var/run/hw-management/"
psu_oper_status = "thermal/psu{}_pwr_status".format(self.index)
#psu_oper_status should always be present for all platforms
self.psu_oper_status = os.path.join(self.psu_path, psu_oper_status)
self._name = "PSU {}".format(psu_index + 1)
if platform in platform_dict_psu:
filemap = psu_profile_list[platform_dict_psu[platform]]
else:
filemap = psu_profile_list[0]
self.psu_data = DEVICE_DATA[platform]['psus']
psu_vpd = filemap[PSU_VPD]
self.model = "N/A"
self.serial = "N/A"
self.rev = "N/A"
if psu_vpd is not None:
self.psu_vpd = os.path.join(self.psu_path, psu_vpd.format(self.index))
self.vpd_data = self._read_vpd_file(self.psu_vpd)
if PN_VPD_FIELD in self.vpd_data:
self.model = self.vpd_data[PN_VPD_FIELD]
else:
logger.log_error("Fail to read PSU{} model number: No key {} in VPD {}".format(self.index, PN_VPD_FIELD, self.psu_vpd))
if SN_VPD_FIELD in self.vpd_data:
self.serial = self.vpd_data[SN_VPD_FIELD]
else:
logger.log_error("Fail to read PSU{} serial number: No key {} in VPD {}".format(self.index, SN_VPD_FIELD, self.psu_vpd))
if REV_VPD_FIELD in self.vpd_data:
self.rev = self.vpd_data[REV_VPD_FIELD]
else:
logger.log_error("Fail to read PSU{} serial number: No key {} in VPD {}".format(self.index, REV_VPD_FIELD, self.psu_vpd))
else:
logger.log_info("Not reading PSU{} VPD data: Platform is fixed".format(self.index))
if not self.psu_data['hot_swappable']:
self.always_present = True
self.psu_voltage = None
self.psu_current = None
self.psu_power = None
self.psu_presence = None
self.psu_temp = None
self.psu_temp_threshold = None
else:
self.always_present = False
psu_voltage = filemap[PSU_VOLTAGE].format(self.index)
psu_voltage = os.path.join(self.psu_path, psu_voltage)
self.psu_voltage = psu_voltage
psu_current = filemap[PSU_CURRENT].format(self.index)
psu_current = os.path.join(self.psu_path, psu_current)
self.psu_current = psu_current
psu_power = filemap[PSU_POWER].format(self.index)
psu_power = os.path.join(self.psu_path, psu_power)
self.psu_power = psu_power
psu_presence = "thermal/psu{}_status".format(self.index)
psu_presence = os.path.join(self.psu_path, psu_presence)
self.psu_presence = psu_presence
self.psu_temp = os.path.join(self.psu_path, 'thermal/psu{}_temp'.format(self.index))
self.psu_temp_threshold = os.path.join(self.psu_path, 'thermal/psu{}_temp_max'.format(self.index))
# unplugable PSU has no FAN
if self.psu_data['hot_swappable']:
fan = Fan(psu_index, None, 1, True, self)
self._fan_list.append(fan)
if self.psu_data['led_num'] == 1:
self.led = ComponentFaultyIndicator(Psu.get_shared_led())
else: # 2010/2100
self.led = PsuLed(self.index)
# initialize thermal for PSU
from .thermal import initialize_psu_thermals
initialize_psu_thermals(platform, self._thermal_list, self.index, self.get_power_available_status)
self._name = "PSU {}".format(self.index)
self.psu_oper_status = os.path.join(PSU_PATH, "thermal/psu{}_pwr_status".format(self.index))
self._led = None
def get_name(self):
return self._name
def _read_vpd_file(self, filename):
"""
Read a vpd file parsed from eeprom with keys and values.
Returns a dictionary.
"""
result = {}
try:
if not os.path.exists(filename):
return result
with open(filename, 'r') as fileobj:
for line in fileobj.readlines():
key, val = line.split(":")
result[key.strip()] = val.strip()
except Exception as e:
logger.log_error("Fail to read VPD file {} due to {}".format(filename, repr(e)))
return result
def _read_generic_file(self, filename, len):
"""
Read a generic file, returns the contents of the file
"""
result = 0
try:
if not os.path.exists(filename):
return result
with open(filename, 'r') as fileobj:
result = int(fileobj.read().strip())
except Exception as e:
logger.log_info("Fail to read file {} due to {}".format(filename, repr(e)))
return result
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
return self.model
return 'N/A'
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
return self.serial
return 'N/A'
def get_revision(self):
"""
Retrieves the hardware revision of the device
Returns:
string: Revision value of device
"""
return self.rev
return 'N/A'
def get_powergood_status(self):
"""
@ -248,10 +66,7 @@ class Psu(PsuBase):
Returns:
bool: True if PSU is operating properly, False if not
"""
status = self._read_generic_file(os.path.join(self.psu_path, self.psu_oper_status), 0)
return status == 1
return utils.read_int_from_file(self.psu_oper_status) == 1
def get_presence(self):
"""
@ -260,12 +75,7 @@ class Psu(PsuBase):
Returns:
bool: True if PSU is present, False if not
"""
if self.always_present:
return self.always_present
else:
status = self._read_generic_file(self.psu_presence, 0)
return status == 1
return True
def get_voltage(self):
"""
@ -275,13 +85,8 @@ class Psu(PsuBase):
A float number, the output voltage in volts,
e.g. 12.1
"""
if self.psu_voltage is not None and self.get_powergood_status():
voltage = self._read_generic_file(self.psu_voltage, 0)
return float(voltage) / 1000
else:
return None
def get_current(self):
"""
Retrieves present electric current supplied by PSU
@ -289,10 +94,6 @@ class Psu(PsuBase):
Returns:
A float number, the electric current in amperes, e.g 15.4
"""
if self.psu_current is not None and self.get_powergood_status():
amperes = self._read_generic_file(self.psu_current, 0)
return float(amperes) / 1000
else:
return None
def get_power(self):
@ -302,12 +103,14 @@ class Psu(PsuBase):
Returns:
A float number, the power in watts, e.g. 302.6
"""
if self.psu_power is not None and self.get_powergood_status():
power = self._read_generic_file(self.psu_power, 0)
return float(power) / 1000000
else:
return None
@property
def led(self):
if not self._led:
self._led = PsuLed(self.index)
return self._led
def set_status_led(self, color):
"""
Sets the state of the PSU status LED
@ -324,7 +127,6 @@ class Psu(PsuBase):
"""
return self.led.set_status(color)
def get_status_led(self):
"""
Gets the state of the PSU status LED
@ -332,12 +134,8 @@ class Psu(PsuBase):
Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above
"""
if self.psu_data['led_num'] == 1:
return Psu.get_shared_led().get_status()
else:
return self.led.get_status()
def get_power_available_status(self):
"""
Gets the power available status
@ -368,13 +166,7 @@ class Psu(PsuBase):
Returns:
bool: True if it is replaceable.
"""
return self.psu_data['hot_swappable']
@classmethod
def get_shared_led(cls):
if not cls.shared_led:
cls.shared_led = SharedLed(PsuLed(None))
return cls.shared_led
return False
def get_temperature(self):
"""
@ -384,12 +176,169 @@ class Psu(PsuBase):
A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125
"""
if self.psu_temp is not None and self.get_powergood_status():
try:
temp = self._read_generic_file(self.psu_temp, 0)
return None
def get_temperature_high_threshold(self):
"""
Retrieves the high threshold temperature of PSU
Returns:
A float number, the high threshold temperature of PSU in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
return None
class Psu(FixedPsu):
"""Platform-specific Psu class"""
PSU_CURRENT = "power/psu{}_curr"
PSU_VOLTAGE = "power/psu{}_volt"
PSU_VOLTAGE1 = "power/psu{}_volt_out2"
PSU_POWER = "power/psu{}_power"
PSU_VPD = "eeprom/psu{}_vpd"
shared_led = None
def __init__(self, psu_index):
super(Psu, self).__init__(psu_index)
psu_voltage = os.path.join(PSU_PATH, self.PSU_VOLTAGE1.format(self.index))
# Workaround for psu voltage sysfs file as the file name differs among platforms
if os.path.exists(psu_voltage):
self.psu_voltage = os.path.join(PSU_PATH, self.PSU_VOLTAGE1.format(self.index))
else:
self.psu_voltage = os.path.join(PSU_PATH, self.PSU_VOLTAGE.format(self.index))
self.psu_current = os.path.join(PSU_PATH, self.PSU_CURRENT.format(self.index))
self.psu_power = os.path.join(PSU_PATH, self.PSU_POWER.format(self.index))
self.psu_presence = os.path.join(PSU_PATH, "thermal/psu{}_status".format(self.index))
self.psu_temp = os.path.join(PSU_PATH, 'thermal/psu{}_temp'.format(self.index))
self.psu_temp_threshold = os.path.join(PSU_PATH, 'thermal/psu{}_temp_max'.format(self.index))
from .fan import PsuFan
self._fan_list.append(PsuFan(psu_index, 1, self))
self.vpd_parser = VpdParser(os.path.join(PSU_PATH, self.PSU_VPD.format(self.index)))
# initialize thermal for PSU
from .thermal import initialize_psu_thermal
self._thermal_list = initialize_psu_thermal(psu_index, self.get_power_available_status)
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
return self.vpd_parser.get_model()
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
return self.vpd_parser.get_serial()
def get_revision(self):
"""
Retrieves the hardware revision of the device
Returns:
string: Revision value of device
"""
return self.vpd_parser.get_revision()
def get_presence(self):
"""
Retrieves the presence status of power supply unit (PSU) defined
Returns:
bool: True if PSU is present, False if not
"""
return utils.read_int_from_file(self.psu_presence) == 1
def get_voltage(self):
"""
Retrieves current PSU voltage output
Returns:
A float number, the output voltage in volts,
e.g. 12.1
"""
if self.get_powergood_status():
# TODO: should we put log_func=None here? If not do this, when a PSU is back to power, some PSU related
# sysfs may not ready, read_int_from_file would encounter exception and log an error.
voltage = utils.read_int_from_file(self.psu_voltage, log_func=logger.log_info)
return float(voltage) / 1000
return None
def get_current(self):
"""
Retrieves present electric current supplied by PSU
Returns:
A float number, the electric current in amperes, e.g 15.4
"""
if self.get_powergood_status():
amperes = utils.read_int_from_file(self.psu_current, log_func=logger.log_info)
return float(amperes) / 1000
return None
def get_power(self):
"""
Retrieves current energy supplied by PSU
Returns:
A float number, the power in watts, e.g. 302.6
"""
if self.get_powergood_status():
power = utils.read_int_from_file(self.psu_power, log_func=logger.log_info)
return float(power) / 1000000
return None
@classmethod
def get_shared_led(cls):
if not cls.shared_led:
cls.shared_led = SharedLed(PsuLed(None))
return cls.shared_led
@property
def led(self):
if not self._led:
self._led = ComponentFaultyIndicator(Psu.get_shared_led())
return self._led
def get_status_led(self):
"""
Gets the state of the PSU status LED
Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above
"""
return Psu.get_shared_led().get_status()
def is_replaceable(self):
"""
Indicate whether this device is replaceable.
Returns:
bool: True if it is replaceable.
"""
return True
def get_temperature(self):
"""
Retrieves current temperature reading from PSU
Returns:
A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125
"""
if self.get_powergood_status():
temp = utils.read_int_from_file(self.psu_temp, log_func=logger.log_info)
return float(temp) / 1000
except Exception as e:
logger.log_info("Fail to get temperature for PSU {} due to - {}".format(self._name, repr(e)))
return None
@ -401,33 +350,8 @@ class Psu(PsuBase):
A float number, the high threshold temperature of PSU in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
if self.psu_temp_threshold is not None and self.get_powergood_status():
try:
temp_threshold = self._read_generic_file(self.psu_temp_threshold, 0)
if self.get_powergood_status():
temp_threshold = utils.read_int_from_file(self.psu_temp_threshold, log_func=logger.log_info)
return float(temp_threshold) / 1000
except Exception as e:
logger.log_info("Fail to get temperature threshold for PSU {} due to - {}".format(self._name, repr(e)))
return None
def get_voltage_high_threshold(self):
"""
Retrieves the high threshold PSU voltage output
Returns:
A float number, the high threshold output voltage in volts,
e.g. 12.1
"""
# hw-management doesn't expose those sysfs for now
raise NotImplementedError
def get_voltage_low_threshold(self):
"""
Retrieves the low threshold PSU voltage output
Returns:
A float number, the low threshold output voltage in volts,
e.g. 12.1
"""
# hw-management doesn't expose those sysfs for now
raise NotImplementedError

View File

@ -36,6 +36,7 @@ try:
from sonic_platform_base.sonic_sfp.qsfp_dd import qsfp_dd_Dom
from sonic_py_common.logger import Logger
from . import utils
from .device_data import DeviceDataManager
except ImportError as e:
raise ImportError (str(e) + "- required module not found")
@ -354,9 +355,25 @@ class SdkHandleContext(object):
deinitialize_sdk_handle(self.sdk_handle)
class SfpCapability:
def __init__(self):
self.dom_supported = False
self.dom_temp_supported = False
self.dom_volt_supported = False
self.dom_rx_power_supported = False
self.dom_tx_bias_power_supported = False
self.dom_tx_power_supported = False
self.dom_tx_disable_supported = False
self.dom_thresholds_supported = False
self.dom_rx_tx_power_bias_supported = False
self.calibration = 0
self.qsfp_page3_available = False
self.second_application_list = False
class SFP(SfpBase):
"""Platform-specific SFP class"""
shared_sdk_handle = None
SFP_MLNX_ERROR_DESCRIPTION_LONGRANGE_NON_MLNX_CABLE = 'Long range for non-Mellanox cable or module'
SFP_MLNX_ERROR_DESCRIPTION_ENFORCE_PART_NUMBER_LIST = 'Enforce part number list'
SFP_MLNX_ERROR_DESCRIPTION_PMD_TYPE_NOT_ENABLED = 'PMD type not enabled'
@ -369,24 +386,240 @@ class SFP(SfpBase):
SFP_MLNX_ERROR_BIT_PCIE_POWER_SLOT_EXCEEDED = 0x00080000
SFP_MLNX_ERROR_BIT_RESERVED = 0x80000000
def __init__(self, sfp_index, sfp_type, sdk_handle_getter, platform):
SfpBase.__init__(self)
def __init__(self, sfp_index, slot_id=0, linecard_port_count=0, lc_name=None):
super(SFP, self).__init__()
if slot_id == 0: # For non-modular chassis
self.index = sfp_index + 1
self.sfp_eeprom_path = "qsfp{}".format(self.index)
self.sfp_status_path = "qsfp{}_status".format(self.index)
self._detect_sfp_type(sfp_type)
self.dom_tx_disable_supported = False
self._dom_capability_detect()
self.sdk_handle_getter = sdk_handle_getter
self.sdk_index = sfp_index
# initialize SFP thermal list
from .thermal import initialize_sfp_thermals
initialize_sfp_thermals(platform, self._thermal_list, self.index)
from .thermal import initialize_sfp_thermal
self._thermal_list = initialize_sfp_thermal(sfp_index)
else: # For modular chassis
# (slot_id % MAX_LC_CONUNT - 1) * MAX_PORT_COUNT + (sfp_index + 1) * (MAX_PORT_COUNT / LC_PORT_COUNT)
max_linecard_count = DeviceDataManager.get_linecard_count()
max_linecard_port_count = DeviceDataManager.get_linecard_max_port_count()
self.index = (slot_id % max_linecard_count - 1) * max_linecard_port_count + sfp_index * (max_linecard_port_count / linecard_port_count) + 1
self.sdk_index = sfp_index
from .thermal import initialize_linecard_sfp_thermal
self._thermal_list = initialize_linecard_sfp_thermal(lc_name, slot_id, sfp_index)
self.slot_id = slot_id
self._sfp_type = None
self._sfp_capability = None
@property
def sdk_handle(self):
return self.sdk_handle_getter()
if not SFP.shared_sdk_handle:
SFP.shared_sdk_handle = initialize_sdk_handle()
if not SFP.shared_sdk_handle:
logger.log_error('Failed to open SDK handle')
return SFP.shared_sdk_handle
@property
def sfp_type(self):
if not self._sfp_type:
eeprom_raw = []
eeprom_raw = self._read_eeprom_specific_bytes(XCVR_TYPE_OFFSET, XCVR_TYPE_WIDTH)
if eeprom_raw:
if eeprom_raw[0] in SFP_TYPE_CODE_LIST:
self._sfp_type = SFP_TYPE
elif eeprom_raw[0] in QSFP_TYPE_CODE_LIST:
self._sfp_type = QSFP_TYPE
elif eeprom_raw[0] in QSFP_DD_TYPE_CODE_LIST:
self._sfp_type = QSFP_DD_TYPE
# we don't regonize this identifier value, treat the xSFP module as the default type
if not self._sfp_type:
raise RuntimeError("Failed to detect SFP type for SFP {}".format(self.index))
else:
return self._sfp_type
def _dom_capability_detect(self):
if self._sfp_capability:
return
self._sfp_capability = SfpCapability()
if not self.get_presence():
return
if self.sfp_type == QSFP_TYPE:
self._sfp_capability.calibration = 1
sfpi_obj = sff8436InterfaceId()
if sfpi_obj is None:
self._sfp_capability.dom_supported = False
offset = 128
# QSFP capability byte parse, through this byte can know whether it support tx_power or not.
# TODO: in the future when decided to migrate to support SFF-8636 instead of SFF-8436,
# need to add more code for determining the capability and version compliance
# in SFF-8636 dom capability definitions evolving with the versions.
qsfp_dom_capability_raw = self._read_eeprom_specific_bytes((offset + XCVR_DOM_CAPABILITY_OFFSET), XCVR_DOM_CAPABILITY_WIDTH)
if qsfp_dom_capability_raw is not None:
qsfp_version_compliance_raw = self._read_eeprom_specific_bytes(QSFP_VERSION_COMPLIANCE_OFFSET, QSFP_VERSION_COMPLIANCE_WIDTH)
qsfp_version_compliance = int(qsfp_version_compliance_raw[0], 16)
dom_capability = sfpi_obj.parse_dom_capability(qsfp_dom_capability_raw, 0)
if qsfp_version_compliance >= 0x08:
self._sfp_capability.dom_temp_supported = dom_capability['data']['Temp_support']['value'] == 'On'
self._sfp_capability.dom_volt_supported = dom_capability['data']['Voltage_support']['value'] == 'On'
self._sfp_capability.dom_rx_power_supported = dom_capability['data']['Rx_power_support']['value'] == 'On'
self._sfp_capability.dom_tx_power_supported = dom_capability['data']['Tx_power_support']['value'] == 'On'
else:
self._sfp_capability.dom_temp_supported = True
self._sfp_capability.dom_volt_supported = True
self._sfp_capability.dom_rx_power_supported = dom_capability['data']['Rx_power_support']['value'] == 'On'
self._sfp_capability.dom_tx_power_supported = True
self._sfp_capability.dom_supported = True
self._sfp_capability.calibration = 1
sfpd_obj = sff8436Dom()
if sfpd_obj is None:
return None
qsfp_option_value_raw = self._read_eeprom_specific_bytes(QSFP_OPTION_VALUE_OFFSET, QSFP_OPTION_VALUE_WIDTH)
if qsfp_option_value_raw is not None:
optional_capability = sfpd_obj.parse_option_params(qsfp_option_value_raw, 0)
self._sfp_capability.dom_tx_disable_supported = optional_capability['data']['TxDisable']['value'] == 'On'
dom_status_indicator = sfpd_obj.parse_dom_status_indicator(qsfp_version_compliance_raw, 1)
self._sfp_capability.qsfp_page3_available = dom_status_indicator['data']['FlatMem']['value'] == 'Off'
else:
self._sfp_capability.dom_supported = False
self._sfp_capability.dom_temp_supported = False
self._sfp_capability.dom_volt_supported = False
self._sfp_capability.dom_rx_power_supported = False
self._sfp_capability.dom_tx_power_supported = False
self._sfp_capability.calibration = 0
self._sfp_capability.qsfp_page3_available = False
elif self.sfp_type == QSFP_DD_TYPE:
sfpi_obj = qsfp_dd_InterfaceId()
if sfpi_obj is None:
self._sfp_capability.dom_supported = False
offset = 0
# two types of QSFP-DD cable types supported: Copper and Optical.
qsfp_dom_capability_raw = self._read_eeprom_specific_bytes((offset + XCVR_DOM_CAPABILITY_OFFSET_QSFP_DD), XCVR_DOM_CAPABILITY_WIDTH_QSFP_DD)
if qsfp_dom_capability_raw is not None:
self._sfp_capability.dom_temp_supported = True
self._sfp_capability.dom_volt_supported = True
dom_capability = sfpi_obj.parse_dom_capability(qsfp_dom_capability_raw, 0)
if dom_capability['data']['Flat_MEM']['value'] == 'Off':
self._sfp_capability.dom_supported = True
self._sfp_capability.second_application_list = True
self._sfp_capability.dom_rx_power_supported = True
self._sfp_capability.dom_tx_power_supported = True
self._sfp_capability.dom_tx_bias_power_supported = True
self._sfp_capability.dom_thresholds_supported = True
self._sfp_capability.dom_rx_tx_power_bias_supported = True
else:
self._sfp_capability.dom_supported = False
self._sfp_capability.second_application_list = False
self._sfp_capability.dom_rx_power_supported = False
self._sfp_capability.dom_tx_power_supported = False
self._sfp_capability.dom_tx_bias_power_supported = False
self._sfp_capability.dom_thresholds_supported = False
self._sfp_capability.dom_rx_tx_power_bias_supported = False
else:
self._sfp_capability.dom_supported = False
self._sfp_capability.dom_temp_supported = False
self._sfp_capability.dom_volt_supported = False
self._sfp_capability.dom_rx_power_supported = False
self._sfp_capability.dom_tx_power_supported = False
self._sfp_capability.dom_tx_bias_power_supported = False
self._sfp_capability.dom_thresholds_supported = False
self._sfp_capability.dom_rx_tx_power_bias_supported = False
elif self.sfp_type == SFP_TYPE:
sfpi_obj = sff8472InterfaceId()
if sfpi_obj is None:
return None
sfp_dom_capability_raw = self._read_eeprom_specific_bytes(XCVR_DOM_CAPABILITY_OFFSET, XCVR_DOM_CAPABILITY_WIDTH)
if sfp_dom_capability_raw is not None:
sfp_dom_capability = int(sfp_dom_capability_raw[0], 16)
self._sfp_capability.dom_supported = (sfp_dom_capability & 0x40 != 0)
if self._sfp_capability.dom_supported:
self._sfp_capability.dom_temp_supported = True
self._sfp_capability.dom_volt_supported = True
self._sfp_capability.dom_rx_power_supported = True
self._sfp_capability.dom_tx_power_supported = True
if sfp_dom_capability & 0x20 != 0:
self._sfp_capability.calibration = 1
elif sfp_dom_capability & 0x10 != 0:
self._sfp_capability.calibration = 2
else:
self._sfp_capability.calibration = 0
else:
self._sfp_capability.dom_temp_supported = False
self._sfp_capability.dom_volt_supported = False
self._sfp_capability.dom_rx_power_supported = False
self._sfp_capability.dom_tx_power_supported = False
self._sfp_capability.calibration = 0
self._sfp_capability.dom_tx_disable_supported = (int(sfp_dom_capability_raw[1], 16) & 0x40 != 0)
else:
self._sfp_capability.dom_supported = False
self._sfp_capability.dom_temp_supported = False
self._sfp_capability.dom_volt_supported = False
self._sfp_capability.dom_rx_power_supported = False
self._sfp_capability.dom_tx_power_supported = False
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_supported(self):
return self._sfp_capability.dom_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_temp_supported(self):
return self._sfp_capability.dom_temp_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_volt_supported(self):
return self._sfp_capability.dom_volt_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_rx_power_supported(self):
return self._sfp_capability.dom_rx_power_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_tx_power_supported(self):
return self._sfp_capability.dom_tx_power_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def calibration(self):
return self._sfp_capability.calibration
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_tx_bias_power_supported(self):
return self._sfp_capability.dom_tx_bias_power_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_tx_disable_supported(self):
return self._sfp_capability.dom_tx_disable_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def qsfp_page3_available(self):
return self._sfp_capability.qsfp_page3_available
@property
@utils.pre_initialize(_dom_capability_detect)
def second_application_list(self):
return self._sfp_capability.second_application_list
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_thresholds_supported(self):
return self._sfp_capability.dom_thresholds_supported
@property
@utils.pre_initialize(_dom_capability_detect)
def dom_rx_tx_power_bias_supported(self):
return self._sfp_capability.dom_rx_tx_power_bias_supported
def reinit(self):
@ -394,8 +627,8 @@ class SFP(SfpBase):
Re-initialize this SFP object when a new SFP inserted
:return:
"""
self._detect_sfp_type(self.sfp_type)
self._dom_capability_detect()
self._sfp_type = None
self._sfp_capability = None
def get_presence(self):
"""
@ -423,11 +656,10 @@ class SFP(SfpBase):
return presence
# Read out any bytes from any offset
def _read_eeprom_specific_bytes(self, offset, num_bytes):
eeprom_raw = []
ethtool_cmd = "ethtool -m sfp{} hex on offset {} length {} 2>/dev/null".format(self.index, offset, num_bytes)
ethtool_cmd = "ethtool -m sfp{} hex on offset {} length {}".format(self.index, offset, num_bytes)
try:
output = subprocess.check_output(ethtool_cmd,
shell=True,
@ -443,158 +675,6 @@ class SFP(SfpBase):
return eeprom_raw
def _detect_sfp_type(self, sfp_type):
eeprom_raw = []
eeprom_raw = self._read_eeprom_specific_bytes(XCVR_TYPE_OFFSET, XCVR_TYPE_WIDTH)
if eeprom_raw:
if eeprom_raw[0] in SFP_TYPE_CODE_LIST:
self.sfp_type = SFP_TYPE
elif eeprom_raw[0] in QSFP_TYPE_CODE_LIST:
self.sfp_type = QSFP_TYPE
elif eeprom_raw[0] in QSFP_DD_TYPE_CODE_LIST:
self.sfp_type = QSFP_DD_TYPE
else:
# we don't regonize this identifier value, treat the xSFP module as the default type
self.sfp_type = sfp_type
logger.log_info("Identifier value of {} module {} is {} which isn't regonized and will be treated as default type ({})".format(
sfp_type, self.index, eeprom_raw[0], sfp_type
))
else:
# eeprom_raw being None indicates the module is not present.
# in this case we treat it as the default type according to the SKU
self.sfp_type = sfp_type
def _dom_capability_detect(self):
if not self.get_presence():
self.dom_supported = False
self.dom_temp_supported = False
self.dom_volt_supported = False
self.dom_rx_power_supported = False
self.dom_tx_bias_power_supported = False
self.dom_tx_power_supported = False
self.calibration = 0
return
if self.sfp_type == QSFP_TYPE:
self.calibration = 1
sfpi_obj = sff8436InterfaceId()
if sfpi_obj is None:
self.dom_supported = False
offset = 128
# QSFP capability byte parse, through this byte can know whether it support tx_power or not.
# TODO: in the future when decided to migrate to support SFF-8636 instead of SFF-8436,
# need to add more code for determining the capability and version compliance
# in SFF-8636 dom capability definitions evolving with the versions.
qsfp_dom_capability_raw = self._read_eeprom_specific_bytes((offset + XCVR_DOM_CAPABILITY_OFFSET), XCVR_DOM_CAPABILITY_WIDTH)
if qsfp_dom_capability_raw is not None:
qsfp_version_compliance_raw = self._read_eeprom_specific_bytes(QSFP_VERSION_COMPLIANCE_OFFSET, QSFP_VERSION_COMPLIANCE_WIDTH)
qsfp_version_compliance = int(qsfp_version_compliance_raw[0], 16)
dom_capability = sfpi_obj.parse_dom_capability(qsfp_dom_capability_raw, 0)
if qsfp_version_compliance >= 0x08:
self.dom_temp_supported = dom_capability['data']['Temp_support']['value'] == 'On'
self.dom_volt_supported = dom_capability['data']['Voltage_support']['value'] == 'On'
self.dom_rx_power_supported = dom_capability['data']['Rx_power_support']['value'] == 'On'
self.dom_tx_power_supported = dom_capability['data']['Tx_power_support']['value'] == 'On'
else:
self.dom_temp_supported = True
self.dom_volt_supported = True
self.dom_rx_power_supported = dom_capability['data']['Rx_power_support']['value'] == 'On'
self.dom_tx_power_supported = True
self.dom_supported = True
self.calibration = 1
sfpd_obj = sff8436Dom()
if sfpd_obj is None:
return None
qsfp_option_value_raw = self._read_eeprom_specific_bytes(QSFP_OPTION_VALUE_OFFSET, QSFP_OPTION_VALUE_WIDTH)
if qsfp_option_value_raw is not None:
optional_capability = sfpd_obj.parse_option_params(qsfp_option_value_raw, 0)
self.dom_tx_disable_supported = optional_capability['data']['TxDisable']['value'] == 'On'
dom_status_indicator = sfpd_obj.parse_dom_status_indicator(qsfp_version_compliance_raw, 1)
self.qsfp_page3_available = dom_status_indicator['data']['FlatMem']['value'] == 'Off'
else:
self.dom_supported = False
self.dom_temp_supported = False
self.dom_volt_supported = False
self.dom_rx_power_supported = False
self.dom_tx_power_supported = False
self.calibration = 0
self.qsfp_page3_available = False
elif self.sfp_type == QSFP_DD_TYPE:
sfpi_obj = qsfp_dd_InterfaceId()
if sfpi_obj is None:
self.dom_supported = False
offset = 0
# two types of QSFP-DD cable types supported: Copper and Optical.
qsfp_dom_capability_raw = self._read_eeprom_specific_bytes((offset + XCVR_DOM_CAPABILITY_OFFSET_QSFP_DD), XCVR_DOM_CAPABILITY_WIDTH_QSFP_DD)
if qsfp_dom_capability_raw is not None:
self.dom_temp_supported = True
self.dom_volt_supported = True
dom_capability = sfpi_obj.parse_dom_capability(qsfp_dom_capability_raw, 0)
if dom_capability['data']['Flat_MEM']['value'] == 'Off':
self.dom_supported = True
self.second_application_list = True
self.dom_rx_power_supported = True
self.dom_tx_power_supported = True
self.dom_tx_bias_power_supported = True
self.dom_thresholds_supported = True
self.dom_rx_tx_power_bias_supported = True
else:
self.dom_supported = False
self.second_application_list = False
self.dom_rx_power_supported = False
self.dom_tx_power_supported = False
self.dom_tx_bias_power_supported = False
self.dom_thresholds_supported = False
self.dom_rx_tx_power_bias_supported = False
else:
self.dom_supported = False
self.dom_temp_supported = False
self.dom_volt_supported = False
self.dom_rx_power_supported = False
self.dom_tx_power_supported = False
self.dom_tx_bias_power_supported = False
self.dom_thresholds_supported = False
self.dom_rx_tx_power_bias_supported = False
elif self.sfp_type == SFP_TYPE:
sfpi_obj = sff8472InterfaceId()
if sfpi_obj is None:
return None
sfp_dom_capability_raw = self._read_eeprom_specific_bytes(XCVR_DOM_CAPABILITY_OFFSET, XCVR_DOM_CAPABILITY_WIDTH)
if sfp_dom_capability_raw is not None:
sfp_dom_capability = int(sfp_dom_capability_raw[0], 16)
self.dom_supported = (sfp_dom_capability & 0x40 != 0)
if self.dom_supported:
self.dom_temp_supported = True
self.dom_volt_supported = True
self.dom_rx_power_supported = True
self.dom_tx_power_supported = True
if sfp_dom_capability & 0x20 != 0:
self.calibration = 1
elif sfp_dom_capability & 0x10 != 0:
self.calibration = 2
else:
self.calibration = 0
else:
self.dom_temp_supported = False
self.dom_volt_supported = False
self.dom_rx_power_supported = False
self.dom_tx_power_supported = False
self.calibration = 0
self.dom_tx_disable_supported = (int(sfp_dom_capability_raw[1], 16) & 0x40 != 0)
else:
self.dom_supported = False
self.dom_temp_supported = False
self.dom_volt_supported = False
self.dom_rx_power_supported = False
self.dom_tx_power_supported = False
def _convert_string_to_num(self, value_str):
if "-inf" in value_str:
return 'N/A'
@ -615,7 +695,6 @@ class SFP(SfpBase):
else:
return 'N/A'
def get_transceiver_info(self):
"""
Retrieves transceiver info of this SFP
@ -706,9 +785,7 @@ class SFP(SfpBase):
elif self.sfp_type == QSFP_TYPE:
offset = 128
vendor_rev_width = XCVR_HW_REV_WIDTH_QSFP
cable_length_width = XCVR_CABLE_LENGTH_WIDTH_QSFP
interface_info_bulk_width = XCVR_INTFACE_BULK_WIDTH_QSFP
sfp_type = 'QSFP'
sfpi_obj = sff8436InterfaceId()
if sfpi_obj is None:
@ -833,9 +910,7 @@ class SFP(SfpBase):
else:
offset = 0
vendor_rev_width = XCVR_HW_REV_WIDTH_SFP
cable_length_width = XCVR_CABLE_LENGTH_WIDTH_SFP
interface_info_bulk_width = XCVR_INTFACE_BULK_WIDTH_SFP
sfp_type = 'SFP'
sfpi_obj = sff8472InterfaceId()
if sfpi_obj is None:
@ -1527,13 +1602,13 @@ class SFP(SfpBase):
@classmethod
def mgmt_phy_mod_pwr_attr_get(cls, power_attr_type, sdk_handle, sdk_index):
def mgmt_phy_mod_pwr_attr_get(cls, power_attr_type, sdk_handle, sdk_index, slot_id):
sx_mgmt_phy_mod_pwr_attr_p = new_sx_mgmt_phy_mod_pwr_attr_t_p()
sx_mgmt_phy_mod_pwr_attr = sx_mgmt_phy_mod_pwr_attr_t()
sx_mgmt_phy_mod_pwr_attr.power_attr_type = power_attr_type
sx_mgmt_phy_mod_pwr_attr_t_p_assign(sx_mgmt_phy_mod_pwr_attr_p, sx_mgmt_phy_mod_pwr_attr)
module_id_info = sx_mgmt_module_id_info_t()
module_id_info.slot_id = 0
module_id_info.slot_id = slot_id
module_id_info.module_id = sdk_index
try:
rc = sx_mgmt_phy_module_pwr_attr_get(sdk_handle, module_id_info, sx_mgmt_phy_mod_pwr_attr_p)
@ -1558,30 +1633,31 @@ class SFP(SfpBase):
# call class level method to avoid initialize the whole sonic platform API
get_lpmode_code = 'from sonic_platform import sfp;\n' \
'with sfp.SdkHandleContext() as sdk_handle:' \
'print(sfp.SFP._get_lpmode(sdk_handle, {}))'.format(self.sdk_index)
'print(sfp.SFP._get_lpmode(sdk_handle, {}, {}))'.format(self.sdk_index, self.slot_id)
lpm_cmd = "docker exec pmon python3 -c \"{}\"".format(get_lpmode_code)
try:
output = subprocess.check_output(lpm_cmd, shell=True, universal_newlines=True)
return 'True' in output
except subprocess.CalledProcessError as e:
print("Error! Unable to get LPM for {}, rc = {}, err msg: {}".format(self.index, e.returncode, e.output))
print("Error! Unable to get LPM for {}, rc = {}, err msg: {}".format(self.sdk_index, e.returncode, e.output))
return False
else:
return self._get_lpmode(self.sdk_handle, self.sdk_index)
return self._get_lpmode(self.sdk_handle, self.sdk_index, self.slot_id)
@classmethod
def _get_lpmode(cls, sdk_handle, sdk_index):
def _get_lpmode(cls, sdk_handle, sdk_index, slot_id):
"""Class level method to get low power mode.
Args:
sdk_handle: SDK handle
sdk_index (integer): SDK port index
slot_id (integer): Slot ID
Returns:
[boolean]: True if low power mode is on else off
"""
_, oper_pwr_mode = cls.mgmt_phy_mod_pwr_attr_get(SX_MGMT_PHY_MOD_PWR_ATTR_PWR_MODE_E, sdk_handle, sdk_index)
_, oper_pwr_mode = cls.mgmt_phy_mod_pwr_attr_get(SX_MGMT_PHY_MOD_PWR_ATTR_PWR_MODE_E, sdk_handle, sdk_index, slot_id)
return oper_pwr_mode == SX_MGMT_PHY_MOD_PWR_MODE_LOW_E
@ -1759,7 +1835,7 @@ class SFP(SfpBase):
if sfpd_obj is None:
return None
if dom_tx_bias_power_supported:
if self.dom_tx_bias_power_supported:
dom_tx_bias_raw = self._read_eeprom_specific_bytes((offset + QSFP_DD_TX_BIAS_OFFSET), QSFP_DD_TX_BIAS_WIDTH)
if dom_tx_bias_raw is not None:
dom_tx_bias_data = sfpd_obj.parse_dom_tx_bias(dom_tx_bias_raw, 0)
@ -1961,28 +2037,28 @@ class SFP(SfpBase):
# call class level method to avoid initialize the whole sonic platform API
reset_code = 'from sonic_platform import sfp;\n' \
'with sfp.SdkHandleContext() as sdk_handle:' \
'print(sfp.SFP._reset(sdk_handle, {}))' \
.format(self.sdk_index)
'print(sfp.SFP._reset(sdk_handle, {}, {}))' \
.format(self.sdk_index, self.slot_id)
reset_cmd = "docker exec pmon python3 -c \"{}\"".format(reset_code)
try:
output = subprocess.check_output(reset_cmd, shell=True, universal_newlines=True)
return 'True' in output
except subprocess.CalledProcessError as e:
print("Error! Unable to set LPM for {}, rc = {}, err msg: {}".format(self.index, e.returncode, e.output))
print("Error! Unable to set LPM for {}, rc = {}, err msg: {}".format(self.sdk_index, e.returncode, e.output))
return False
else:
return self._reset(self.sdk_handle, self.sdk_index)
return self._reset(self.sdk_handle, self.sdk_index, self.slot_id)
@classmethod
def _reset(cls, sdk_handle, sdk_index):
def _reset(cls, sdk_handle, sdk_index, slot_id):
module_id_info = sx_mgmt_module_id_info_t()
module_id_info.slot_id = 0
module_id_info.slot_id = slot_id
module_id_info.module_id = sdk_index
rc = sx_mgmt_phy_module_reset(sdk_handle, module_id_info)
if rc != SX_STATUS_SUCCESS:
logger.log_error("Error occurred when resetting SFP module {}, error code {}".format(sdk_index, rc))
logger.log_error("Error occurred when resetting SFP module {}, slot {}, error code {}".format(sdk_index, slot_id, rc))
return rc == SX_STATUS_SUCCESS
@ -2046,10 +2122,7 @@ class SFP(SfpBase):
delete_sx_port_admin_state_t_p(admin_state_p)
delete_sx_port_module_state_t_p(module_state_p)
if admin_state == SX_PORT_ADMIN_STATUS_UP:
return True
else:
return False
return admin_state == SX_PORT_ADMIN_STATUS_UP
@classmethod
@ -2062,7 +2135,7 @@ class SFP(SfpBase):
@classmethod
def get_logical_ports(cls, sdk_handle, sdk_index):
def get_logical_ports(cls, sdk_handle, sdk_index, slot_id):
# Get all the ports related to the sfp, if port admin status is up, put it to list
port_attributes_list = new_sx_port_attributes_t_arr(SX_PORT_ATTR_ARR_SIZE)
port_cnt_p = new_uint32_t_p()
@ -2078,6 +2151,7 @@ class SFP(SfpBase):
if not cls.is_nve(int(port_attributes.log_port)) \
and not cls.is_cpu(int(port_attributes.log_port)) \
and port_attributes.port_mapping.module_port == sdk_index \
and port_attributes.port_mapping.slot == slot_id \
and cls.is_port_admin_status_up(sdk_handle, port_attributes.log_port):
log_port_list.append(port_attributes.log_port)
@ -2087,7 +2161,7 @@ class SFP(SfpBase):
@classmethod
def mgmt_phy_mod_pwr_attr_set(cls, sdk_handle, sdk_index, power_attr_type, admin_pwr_mode):
def mgmt_phy_mod_pwr_attr_set(cls, sdk_handle, sdk_index, slot_id, power_attr_type, admin_pwr_mode):
result = False
sx_mgmt_phy_mod_pwr_attr = sx_mgmt_phy_mod_pwr_attr_t()
sx_mgmt_phy_mod_pwr_mode_attr = sx_mgmt_phy_mod_pwr_mode_attr_t()
@ -2097,12 +2171,12 @@ class SFP(SfpBase):
sx_mgmt_phy_mod_pwr_attr_p = new_sx_mgmt_phy_mod_pwr_attr_t_p()
sx_mgmt_phy_mod_pwr_attr_t_p_assign(sx_mgmt_phy_mod_pwr_attr_p, sx_mgmt_phy_mod_pwr_attr)
module_id_info = sx_mgmt_module_id_info_t()
module_id_info.slot_id = 0
module_id_info.slot_id = slot_id
module_id_info.module_id = sdk_index
try:
rc = sx_mgmt_phy_module_pwr_attr_set(sdk_handle, SX_ACCESS_CMD_SET, module_id_info, sx_mgmt_phy_mod_pwr_attr_p)
if SX_STATUS_SUCCESS != rc:
logger.log_error("Error occurred when setting power mode for SFP module {}, error code {}".format(sdk_index, rc))
logger.log_error("Error occurred when setting power mode for SFP module {}, slot {}, error code {}".format(sdk_index, slot_id, rc))
result = False
else:
result = True
@ -2113,10 +2187,10 @@ class SFP(SfpBase):
@classmethod
def _set_lpmode_raw(cls, sdk_handle, sdk_index, ports, attr_type, power_mode):
def _set_lpmode_raw(cls, sdk_handle, sdk_index, slot_id, ports, attr_type, power_mode):
result = False
# Check if the module already works in the same mode
admin_pwr_mode, oper_pwr_mode = cls.mgmt_phy_mod_pwr_attr_get(attr_type, sdk_handle, sdk_index)
admin_pwr_mode, oper_pwr_mode = cls.mgmt_phy_mod_pwr_attr_get(attr_type, sdk_handle, sdk_index, slot_id)
if (power_mode == SX_MGMT_PHY_MOD_PWR_MODE_LOW_E and oper_pwr_mode == SX_MGMT_PHY_MOD_PWR_MODE_LOW_E) \
or (power_mode == SX_MGMT_PHY_MOD_PWR_MODE_AUTO_E and admin_pwr_mode == SX_MGMT_PHY_MOD_PWR_MODE_AUTO_E):
return True
@ -2125,7 +2199,7 @@ class SFP(SfpBase):
for port in ports:
cls.set_port_admin_status_by_log_port(sdk_handle, port, SX_PORT_ADMIN_STATUS_DOWN)
# Set the desired power mode
result = cls.mgmt_phy_mod_pwr_attr_set(sdk_handle, sdk_index, attr_type, power_mode)
result = cls.mgmt_phy_mod_pwr_attr_set(sdk_handle, sdk_index, slot_id, attr_type, power_mode)
finally:
# Bring the port up
for port in ports:
@ -2150,8 +2224,8 @@ class SFP(SfpBase):
# call class level method to avoid initialize the whole sonic platform API
set_lpmode_code = 'from sonic_platform import sfp;\n' \
'with sfp.SdkHandleContext() as sdk_handle:' \
'print(sfp.SFP._set_lpmode({}, sdk_handle, {}))' \
.format('True' if lpmode else 'False', self.sdk_index)
'print(sfp.SFP._set_lpmode({}, sdk_handle, {}, {}))' \
.format('True' if lpmode else 'False', self.sdk_index, self.slot_id)
lpm_cmd = "docker exec pmon python3 -c \"{}\"".format(set_lpmode_code)
# Set LPM
@ -2159,22 +2233,23 @@ class SFP(SfpBase):
output = subprocess.check_output(lpm_cmd, shell=True, universal_newlines=True)
return 'True' in output
except subprocess.CalledProcessError as e:
print("Error! Unable to set LPM for {}, rc = {}, err msg: {}".format(self.index, e.returncode, e.output))
print("Error! Unable to set LPM for {}, rc = {}, err msg: {}".format(self.sdk_index, e.returncode, e.output))
return False
else:
return self._set_lpmode(lpmode, self.sdk_handle, self.sdk_index)
return self._set_lpmode(lpmode, self.sdk_handle, self.sdk_index, self.slot_id)
@classmethod
def _set_lpmode(cls, lpmode, sdk_handle, sdk_index):
log_port_list = cls.get_logical_ports(sdk_handle, sdk_index)
def _set_lpmode(cls, lpmode, sdk_handle, sdk_index, slot_id):
log_port_list = cls.get_logical_ports(sdk_handle, sdk_index, slot_id)
sdk_lpmode = SX_MGMT_PHY_MOD_PWR_MODE_LOW_E if lpmode else SX_MGMT_PHY_MOD_PWR_MODE_AUTO_E
cls._set_lpmode_raw(sdk_handle,
sdk_index,
slot_id,
log_port_list,
SX_MGMT_PHY_MOD_PWR_ATTR_PWR_MODE_E,
sdk_lpmode)
logger.log_info("{} low power mode for module {}".format("Enabled" if lpmode else "Disabled", sdk_index))
logger.log_info("{} low power mode for module {}, slot {}".format("Enabled" if lpmode else "Disabled", sdk_index, slot_id))
return True

View File

@ -23,14 +23,19 @@ import sys, errno
import os
import time
import select
if 'MLNX_PLATFORM_API_UNIT_TESTING' not in os.environ:
from .device_data import DeviceDataManager
try:
if 'PLATFORM_API_UNIT_TESTING' not in os.environ:
from python_sdk_api.sx_api import *
else:
else:
from mock import MagicMock
class MockSxFd(object):
fd = 99
new_sx_fd_t_p = MagicMock(return_value=MockSxFd())
new_sx_user_channel_t_p = MagicMock()
except KeyError:
pass
from sonic_py_common.logger import Logger
from .sfp import SFP
@ -253,6 +258,7 @@ class sfp_event:
try:
read, _, _ = select.select([self.rx_fd_p.fd], [], [], timeout)
print(read)
except select.error as err:
rc, msg = err
if rc == errno.EAGAIN or rc == errno.EINTR:
@ -263,6 +269,7 @@ class sfp_event:
for fd in read:
if fd == self.rx_fd_p.fd:
success, port_list, module_state, error_type = self.on_pmpe(self.rx_fd_p)
print('success = ', success)
if not success:
logger.log_error("failed to read from {}".format(fd))
break
@ -339,6 +346,7 @@ class sfp_event:
module_state = pmpe_t.module_state
error_type = pmpe_t.error_type
module_id = pmpe_t.module_id
slot_id = pmpe_t.slot_id # For non-modular chassis, it should return 0
if module_state == SDK_SFP_STATE_ERR:
logger.log_error("Receive PMPE error event on module {}: status {} error type {}".format(module_id, module_state, error_type))
@ -352,13 +360,16 @@ class sfp_event:
logical_port = sx_port_log_id_t_arr_getitem(logical_port_list, i)
rc = sx_api_port_device_get(self.handle, 1 , 0, port_attributes_list, port_cnt_p)
port_cnt = uint32_t_p_value(port_cnt_p)
x = 0 # x is the port index within a LC
for i in range(port_cnt):
port_attributes = sx_port_attributes_t_arr_getitem(port_attributes_list,i)
if port_attributes.log_port == logical_port:
label_port = port_attributes.port_mapping.module_port
label_port = slot_id * DeviceDataManager.get_linecard_max_port_count() + x + 1
break
if port_attributes.port_mapping.slot_id == slot_id:
x += 1
if label_port is not None:
label_port_list.append(label_port)

View File

@ -25,406 +25,241 @@
try:
from sonic_platform_base.thermal_base import ThermalBase
from sonic_py_common.logger import Logger
from os import listdir
from os.path import isfile, join
import io
import os.path
import copy
import os
import glob
from .device_data import DeviceDataManager
from . import utils
except ImportError as e:
raise ImportError (str(e) + "- required module not found")
# Global logger class instance
logger = Logger()
THERMAL_DEV_CATEGORY_CPU_CORE = "cpu_core"
THERMAL_DEV_CATEGORY_CPU_PACK = "cpu_pack"
THERMAL_DEV_CATEGORY_MODULE = "module"
THERMAL_DEV_CATEGORY_PSU = "psu"
THERMAL_DEV_CATEGORY_GEARBOX = "gearbox"
THERMAL_DEV_CATEGORY_AMBIENT = "ambient"
"""
The most important information for creating a Thermal object is 3 sysfs files: temperature file, high threshold file and
high critical threshold file. There is no common naming rule for thermal objects on Nvidia platform. There are two types
of thermal object: single and indexable:
1. Single. Such as asic, port_amb...
2. Indexablt. Such as cpu_core0, cpu_core1, psu1_temp, psu2_temp
THERMAL_DEV_ASIC_AMBIENT = "asic_amb"
THERMAL_DEV_FAN_AMBIENT = "fan_amb"
THERMAL_DEV_PORT_AMBIENT = "port_amb"
THERMAL_DEV_COMEX_AMBIENT = "comex_amb"
THERMAL_DEV_BOARD_AMBIENT = "board_amb"
Thermal objects can be created according to a pre-defined naming rule. The naming rules contains following fields
THERMAL_API_GET_TEMPERATURE = "get_temperature"
THERMAL_API_GET_HIGH_THRESHOLD = "get_high_threshold"
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD = "get_high_critical_threshold"
THERMAL_API_INVALID_HIGH_THRESHOLD = 0.0
HW_MGMT_THERMAL_ROOT = "/var/run/hw-management/thermal/"
THERMAL_ZONE_ASIC_PATH = "/var/run/hw-management/thermal/mlxsw/"
THERMAL_ZONE_MODULE_PATH = "/var/run/hw-management/thermal/mlxsw-module{}/"
THERMAL_ZONE_GEARBOX_PATH = "/var/run/hw-management/thermal/mlxsw-gearbox{}/"
THERMAL_ZONE_MODE = "thermal_zone_mode"
THERMAL_ZONE_POLICY = "thermal_zone_policy"
THERMAL_ZONE_TEMPERATURE = "thermal_zone_temp"
THERMAL_ZONE_NORMAL_TEMPERATURE = "temp_trip_high"
MODULE_TEMPERATURE_FAULT_PATH = "/var/run/hw-management/thermal/module{}_temp_fault"
thermal_api_handler_asic = {
THERMAL_API_GET_TEMPERATURE: 'asic',
THERMAL_API_GET_HIGH_THRESHOLD: 'mlxsw/temp_trip_hot',
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD: 'mlxsw/temp_trip_crit'
}
thermal_api_handler_cpu_core = {
THERMAL_API_GET_TEMPERATURE:"cpu_core{}",
THERMAL_API_GET_HIGH_THRESHOLD:"cpu_core{}_max",
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"cpu_core{}_crit"
}
thermal_api_handler_cpu_pack = {
THERMAL_API_GET_TEMPERATURE:"cpu_pack",
THERMAL_API_GET_HIGH_THRESHOLD:"cpu_pack_max",
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"cpu_pack_crit"
}
thermal_api_handler_module = {
THERMAL_API_GET_TEMPERATURE:"module{}_temp_input",
THERMAL_API_GET_HIGH_THRESHOLD:"module{}_temp_crit",
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"module{}_temp_emergency"
}
thermal_api_handler_psu = {
THERMAL_API_GET_TEMPERATURE:"psu{}_temp",
THERMAL_API_GET_HIGH_THRESHOLD:"psu{}_temp_max",
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:None
}
thermal_api_handler_gearbox = {
THERMAL_API_GET_TEMPERATURE:"gearbox{}_temp_input",
THERMAL_API_GET_HIGH_THRESHOLD:"mlxsw-gearbox{}/temp_trip_hot",
THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"mlxsw-gearbox{}/temp_trip_crit"
}
thermal_ambient_apis = {
THERMAL_DEV_ASIC_AMBIENT : thermal_api_handler_asic,
THERMAL_DEV_PORT_AMBIENT : "port_amb",
THERMAL_DEV_FAN_AMBIENT : "fan_amb",
THERMAL_DEV_COMEX_AMBIENT : "comex_amb",
THERMAL_DEV_BOARD_AMBIENT : "board_amb"
}
thermal_ambient_name = {
THERMAL_DEV_ASIC_AMBIENT : 'ASIC',
THERMAL_DEV_PORT_AMBIENT : "Ambient Port Side Temp",
THERMAL_DEV_FAN_AMBIENT : "Ambient Fan Side Temp",
THERMAL_DEV_COMEX_AMBIENT : "Ambient COMEX Temp",
THERMAL_DEV_BOARD_AMBIENT : "Ambient Board Temp"
}
thermal_api_handlers = {
THERMAL_DEV_CATEGORY_CPU_CORE : thermal_api_handler_cpu_core,
THERMAL_DEV_CATEGORY_CPU_PACK : thermal_api_handler_cpu_pack,
THERMAL_DEV_CATEGORY_MODULE : thermal_api_handler_module,
THERMAL_DEV_CATEGORY_PSU : thermal_api_handler_psu,
THERMAL_DEV_CATEGORY_GEARBOX : thermal_api_handler_gearbox
}
thermal_name = {
THERMAL_DEV_CATEGORY_CPU_CORE : "CPU Core {} Temp",
THERMAL_DEV_CATEGORY_CPU_PACK : "CPU Pack Temp",
THERMAL_DEV_CATEGORY_MODULE : "xSFP module {} Temp",
THERMAL_DEV_CATEGORY_PSU : "PSU-{} Temp",
THERMAL_DEV_CATEGORY_GEARBOX : "Gearbox {} Temp"
}
thermal_device_categories_all = [
THERMAL_DEV_CATEGORY_AMBIENT,
THERMAL_DEV_CATEGORY_CPU_PACK,
THERMAL_DEV_CATEGORY_CPU_CORE,
THERMAL_DEV_CATEGORY_GEARBOX,
]
thermal_device_categories_singleton = [
THERMAL_DEV_CATEGORY_CPU_PACK,
THERMAL_DEV_CATEGORY_AMBIENT
]
thermal_api_names = [
THERMAL_API_GET_TEMPERATURE,
THERMAL_API_GET_HIGH_THRESHOLD
]
platform_dict_thermal = {'x86_64-mlnx_msn2700-r0': 0, 'x86_64-mlnx_lssn2700-r0': 0, 'x86_64-mlnx_msn2740-r0': 3,
'x86_64-mlnx_msn2100-r0': 1, 'x86_64-mlnx_msn2410-r0': 2, 'x86_64-mlnx_msn2010-r0': 4,
'x86_64-mlnx_msn3420-r0': 9, 'x86_64-mlnx_msn3700-r0': 5, 'x86_64-mlnx_msn3700c-r0': 6,
'x86_64-mlnx_msn3800-r0': 7, 'x86_64-mlnx_msn4600-r0': 12, 'x86_64-mlnx_msn4600c-r0': 9,
'x86_64-mlnx_msn4700-r0': 8, 'x86_64-mlnx_msn4410-r0': 8}
thermal_profile_list = [
# 0 2700
Field Name Mandatory Default Description
name M Thermal object name template
temperature M Temperature file name
high_threshold O None High threshold file name
high_critical_threshold O None High critical threshold file name
type O single Thermal object type
start_index O 1 Thermal object start index, only used by indexable thermal object
"""
THERMAL_NAMING_RULE = {
"sfp thermals":
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2),
THERMAL_DEV_CATEGORY_MODULE:(1, 32),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
"name": "xSFP module {} Temp",
"temperature": "module{}_temp_input",
"high_threshold": "module{}_temp_crit",
"high_critical_threshold": "module{}_temp_emergency",
"type": "indexable"
},
# 1 2100
"psu thermals":
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 16),
THERMAL_DEV_CATEGORY_PSU:(0, 0),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,0),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT,
]
)
"name": "PSU-{} Temp",
"temperature": "psu{}_temp",
"high_threshold": "psu{}_temp_max",
"type": "indexable"
},
# 2 2410
"chassis thermals": [
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2),
THERMAL_DEV_CATEGORY_MODULE:(1, 56),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT,
]
)
"name": "ASIC",
"temperature": "asic",
"high_threshold": "mlxsw/temp_trip_hot",
"high_critical_threshold": "mlxsw/temp_trip_crit"
},
# 3 2740
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 32),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,0),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT,
]
)
"name": "Ambient Port Side Temp",
"temperature": "port_amb"
},
# 4 2010
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 22),
THERMAL_DEV_CATEGORY_PSU:(0, 0),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,0),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT,
]
)
"name": "Ambient Fan Side Temp",
"temperature": "fan_amb"
},
# 5 3700
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 32),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
"name": "Ambient COMEX Temp",
"temperature": "comex_amb"
},
# 6 3700c
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2),
THERMAL_DEV_CATEGORY_MODULE:(1, 32),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
"name": "CPU Pack Temp",
"temperature": "cpu_pack",
"high_threshold": "cpu_pack_max",
"high_critical_threshold": "cpu_pack_crit"
},
# 7 3800
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 64),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(1,32),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
"name": "CPU Core {} Temp",
"temperature": "cpu_core{}",
"high_threshold": "cpu_core{}_max",
"high_critical_threshold": "cpu_core{}_crit",
"type": "indexable",
"start_index": 0
},
# 8 4700
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 32),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
},
# 9 3420
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2),
THERMAL_DEV_CATEGORY_MODULE:(1, 60),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
},
# 10 4600C
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 64),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
},
# 11 4410
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 32),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
},
# 12 4600
{
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4),
THERMAL_DEV_CATEGORY_MODULE:(1, 64),
THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),
THERMAL_DEV_CATEGORY_GEARBOX:(0,0),
THERMAL_DEV_CATEGORY_AMBIENT:(0,
[
THERMAL_DEV_ASIC_AMBIENT,
THERMAL_DEV_COMEX_AMBIENT,
THERMAL_DEV_PORT_AMBIENT,
THERMAL_DEV_FAN_AMBIENT
]
)
"name": "Gearbox {} Temp",
"temperature": "gearbox{}_temp_input",
"high_threshold": "mlxsw-gearbox{}/temp_trip_hot",
"high_critical_threshold": "mlxsw-gearbox{}/temp_trip_crit",
"type": "indexable"
}
]
],
'linecard thermals': {
"name": "Gearbox {} Temp",
"temperature": "gearbox{}_temp_input",
"high_threshold": "mlxsw-gearbox{}/temp_trip_hot",
"high_critical_threshold": "mlxsw-gearbox{}/temp_trip_crit",
"type": "indexable"
}
}
def initialize_psu_thermals(platform, thermal_list, psu_index, dependency):
tp_index = platform_dict_thermal[platform]
thermal_profile = thermal_profile_list[tp_index]
_, count = thermal_profile[THERMAL_DEV_CATEGORY_PSU]
if count == 0:
return
thermal = Thermal(THERMAL_DEV_CATEGORY_PSU, psu_index, True, 1, dependency)
thermal_list.append(thermal)
CHASSIS_THERMAL_SYSFS_FOLDER = '/run/hw-management/thermal'
THERMAL_ZONE_FOLDER_WILDCARD = '/run/hw-management/thermal/mlxsw*'
THERMAL_ZONE_POLICY_FILE = 'thermal_zone_policy'
THERMAL_ZONE_MODE_FILE = 'thermal_zone_mode'
THERMAL_ZONE_TEMP_FILE = 'thermal_zone_temp'
THERMAL_ZONE_THRESHOLD_FILE = 'temp_trip_high'
MODULE_TEMP_FAULT_WILDCARRD = '/run/hw-management/thermal/module*_temp_fault'
MAX_AMBIENT_TEMP = 120
def initialize_sfp_thermals(platform, thermal_list, sfp_index):
thermal = Thermal(THERMAL_DEV_CATEGORY_MODULE, sfp_index, True, 1)
thermal_list.append(thermal)
def initialize_chassis_thermals(platform, thermal_list):
# create thermal objects for all categories of sensors
tp_index = platform_dict_thermal[platform]
thermal_profile = thermal_profile_list[tp_index]
Thermal.thermal_profile = thermal_profile
def initialize_chassis_thermals():
thermal_list = []
rules = THERMAL_NAMING_RULE['chassis thermals']
position = 1
for category in thermal_device_categories_all:
if category == THERMAL_DEV_CATEGORY_AMBIENT:
count, ambient_list = thermal_profile[category]
for ambient in ambient_list:
thermal = Thermal(category, ambient, True, position)
thermal_list.append(thermal),
position += 1
else:
start, count = 0, 0
if category in thermal_profile:
start, count = thermal_profile[category]
for rule in rules:
if 'type' in rule and rule['type'] == 'indexable':
count = 0
if 'Gearbox' in rule['name']:
count = DeviceDataManager.get_gearbox_count('/run/hw-management/config')
elif 'CPU Core' in rule['name']:
count = DeviceDataManager.get_cpu_thermal_count()
if count == 0:
logger.log_debug('Failed to get thermal object count for {}'.format(rule['name']))
continue
if count == 1:
thermal = Thermal(category, 0, False, position)
thermal_list.append(thermal)
for index in range(count):
thermal_list.append(create_indexable_thermal(rule, index, CHASSIS_THERMAL_SYSFS_FOLDER, position))
position += 1
else:
for index in range(count):
thermal = Thermal(category, start + index, True, position)
thermal_list.append(thermal)
thermal_object = create_single_thermal(rule, CHASSIS_THERMAL_SYSFS_FOLDER, position)
if thermal_object:
thermal_list.append(thermal_object)
position += 1
return thermal_list
def initialize_psu_thermal(psu_index, presence_cb):
"""Initialize PSU thermal object
Args:
psu_index (int): PSU index, 0-based
presence_cb (function): A callback function to indicate if the thermal is present. When removing a PSU, the related
thermal sysfs files will be removed from system, presence_cb is used to check such situation and avoid printing
error logs.
Returns:
[list]: A list of thermal objects
"""
return [create_indexable_thermal(THERMAL_NAMING_RULE['psu thermals'], psu_index, CHASSIS_THERMAL_SYSFS_FOLDER, 1, presence_cb)]
def initialize_sfp_thermal(sfp_index):
return [create_indexable_thermal(THERMAL_NAMING_RULE['sfp thermals'], sfp_index, CHASSIS_THERMAL_SYSFS_FOLDER, 1)]
def initialize_linecard_thermals(lc_name, lc_index):
thermal_list = []
rule = THERMAL_NAMING_RULE['linecard thermals']
rule = copy.deepcopy(rule)
rule['name'] = '{} {}'.format(lc_name, rule['name'])
sysfs_folder = '/run/hw-management/lc{}/thermal'.format(lc_index)
count = DeviceDataManager.get_gearbox_count('/run/hw-management/lc{}/config'.format(lc_index))
for index in range(count):
thermal_list.append(create_indexable_thermal(rule, index, sysfs_folder, index + 1))
return thermal_list
def initialize_linecard_sfp_thermal(lc_name, lc_index, sfp_index):
rule = THERMAL_NAMING_RULE['sfp thermals']
rule = copy.deepcopy(rule)
rule['name'] = '{} {}'.format(lc_name, rule['name'])
sysfs_folder = '/run/hw-management/lc{}/thermal'.format(lc_index)
return [create_indexable_thermal(rule, sfp_index, sysfs_folder, 1)]
def create_indexable_thermal(rule, index, sysfs_folder, position, presence_cb=None):
index += rule.get('start_index', 1)
name = rule['name'].format(index)
temp_file = os.path.join(sysfs_folder, rule['temperature'].format(index))
_check_thermal_sysfs_existence(temp_file)
if 'high_threshold' in rule:
high_th_file = os.path.join(sysfs_folder, rule['high_threshold'].format(index))
_check_thermal_sysfs_existence(high_th_file)
else:
high_th_file = None
if 'high_critical_threshold' in rule:
high_crit_th_file = os.path.join(sysfs_folder, rule['high_critical_threshold'].format(index))
_check_thermal_sysfs_existence(high_crit_th_file)
else:
high_crit_th_file = None
if not presence_cb:
return Thermal(name, temp_file, high_th_file, high_crit_th_file, position)
else:
return RemovableThermal(name, temp_file, high_th_file, high_crit_th_file, position, presence_cb)
def create_single_thermal(rule, sysfs_folder, position, presence_cb=None):
temp_file = rule['temperature']
thermal_capability = DeviceDataManager.get_thermal_capability()
if thermal_capability:
if not thermal_capability.get(temp_file, True):
return None
temp_file = os.path.join(sysfs_folder, temp_file)
_check_thermal_sysfs_existence(temp_file)
if 'high_threshold' in rule:
high_th_file = os.path.join(sysfs_folder, rule['high_threshold'])
_check_thermal_sysfs_existence(high_th_file)
else:
high_th_file = None
if 'high_critical_threshold' in rule:
high_crit_th_file = os.path.join(sysfs_folder, rule['high_critical_threshold'])
_check_thermal_sysfs_existence(high_crit_th_file)
else:
high_crit_th_file = None
name = rule['name']
if not presence_cb:
return Thermal(name, temp_file, high_th_file, high_crit_th_file, position)
else:
return RemovableThermal(name, temp_file, high_th_file, high_crit_th_file, position, presence_cb)
def _check_thermal_sysfs_existence(file_path):
if not os.path.exists(file_path):
logger.log_error('Thermal sysfs {} does not exist'.format(file_path))
class Thermal(ThermalBase):
thermal_profile = None
thermal_algorithm_status = False
def __init__(self, category, index, has_index, position, dependency = None):
def __init__(self, name, temp_file, high_th_file, high_crit_th_file, position):
"""
index should be a string for category ambient and int for other categories
"""
super(Thermal, self).__init__()
if category == THERMAL_DEV_CATEGORY_AMBIENT:
self.name = thermal_ambient_name[index]
self.index = index
elif has_index:
self.name = thermal_name[category].format(index)
self.index = index
else:
self.name = thermal_name[category]
self.index = 0
self.category = category
self.name = name
self.position = position
self.temperature = self._get_file_from_api(THERMAL_API_GET_TEMPERATURE)
self.high_threshold = self._get_file_from_api(THERMAL_API_GET_HIGH_THRESHOLD)
self.high_critical_threshold = self._get_file_from_api(THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD)
self.dependency = dependency
self.temperature = temp_file
self.high_threshold = high_th_file
self.high_critical_threshold = high_crit_th_file
def get_name(self):
"""
@ -435,45 +270,6 @@ class Thermal(ThermalBase):
"""
return self.name
@classmethod
def _read_generic_file(cls, filename, len):
"""
Read a generic file, returns the contents of the file
"""
result = None
try:
with open(filename, 'r') as fileobj:
result = fileobj.read().strip()
except Exception as e:
logger.log_info("Fail to read file {} due to {}".format(filename, repr(e)))
return result
def _get_file_from_api(self, api_name):
if self.category == THERMAL_DEV_CATEGORY_AMBIENT:
handler = thermal_ambient_apis[self.index]
if isinstance(handler, str):
if api_name == THERMAL_API_GET_TEMPERATURE:
filename = thermal_ambient_apis[self.index]
else:
return None
elif isinstance(handler, dict):
filename = handler[api_name]
else:
return None
else:
handler = thermal_api_handlers[self.category][api_name]
if self.category in thermal_device_categories_singleton:
filename = handler
else:
if handler:
filename = handler.format(self.index)
else:
return None
return join(HW_MGMT_THERMAL_ROOT, filename)
def get_temperature(self):
"""
Retrieves current temperature reading from thermal
@ -482,19 +278,8 @@ class Thermal(ThermalBase):
A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125
"""
if self.dependency:
status, hint = self.dependency()
if not status:
logger.log_debug("get_temperature for {} failed due to {}".format(self.name, hint))
return None
value_str = self._read_generic_file(self.temperature, 0)
if value_str is None:
return None
value_float = float(value_str)
if self.category == THERMAL_DEV_CATEGORY_MODULE and value_float == THERMAL_API_INVALID_HIGH_THRESHOLD:
return None
return value_float / 1000.0
value = utils.read_float_from_file(self.temperature, None, log_func=logger.log_info)
return value / 1000.0 if (value is not None and value != 0) else None
def get_high_threshold(self):
"""
@ -506,19 +291,8 @@ class Thermal(ThermalBase):
"""
if self.high_threshold is None:
return None
if self.dependency:
status, hint = self.dependency()
if not status:
logger.log_debug("get_high_threshold for {} failed due to {}".format(self.name, hint))
return None
value_str = self._read_generic_file(self.high_threshold, 0)
if value_str is None:
return None
value_float = float(value_str)
if self.category == THERMAL_DEV_CATEGORY_MODULE and value_float == THERMAL_API_INVALID_HIGH_THRESHOLD:
return None
return value_float / 1000.0
value = utils.read_float_from_file(self.high_threshold, None, log_func=logger.log_info)
return value / 1000.0 if (value is not None and value != 0) else None
def get_high_critical_threshold(self):
"""
@ -530,18 +304,8 @@ class Thermal(ThermalBase):
"""
if self.high_critical_threshold is None:
return None
if self.dependency:
status, hint = self.dependency()
if not status:
logger.log_debug("get_high_critical_threshold for {} failed due to {}".format(self.name, hint))
return None
value_str = self._read_generic_file(self.high_critical_threshold, 0)
if value_str is None:
return None
value_float = float(value_str)
if self.category == THERMAL_DEV_CATEGORY_MODULE and value_float == THERMAL_API_INVALID_HIGH_THRESHOLD:
return None
return value_float / 1000.0
value = utils.read_float_from_file(self.high_critical_threshold, None, log_func=logger.log_info)
return value / 1000.0 if (value is not None and value != 0) else None
def get_position_in_parent(self):
"""
@ -559,20 +323,6 @@ class Thermal(ThermalBase):
"""
return False
@classmethod
def _write_generic_file(cls, filename, content):
"""
Generic functions to write content to a specified file path if
the content has changed.
"""
try:
with open(filename, 'w+') as file_obj:
origin_content = file_obj.read()
if origin_content != content:
file_obj.write(content)
except Exception as e:
logger.log_info("Fail to write file {} due to {}".format(filename, repr(e)))
@classmethod
def set_thermal_algorithm_status(cls, status, force=True):
"""
@ -589,31 +339,18 @@ class Thermal(ThermalBase):
Returns:
True if thermal algorithm status changed.
"""
if not cls.thermal_profile:
raise Exception("Fail to get thermal profile for this switch")
if not force and cls.thermal_algorithm_status == status:
return False
cls.thermal_algorithm_status = status
content = "enabled" if status else "disabled"
mode = "enabled" if status else "disabled"
policy = "step_wise" if status else "user_space"
cls._write_generic_file(join(THERMAL_ZONE_ASIC_PATH, THERMAL_ZONE_MODE), content)
cls._write_generic_file(join(THERMAL_ZONE_ASIC_PATH, THERMAL_ZONE_POLICY), policy)
for thermal_zone_folder in glob.iglob(THERMAL_ZONE_FOLDER_WILDCARD):
policy_file = os.path.join(thermal_zone_folder, THERMAL_ZONE_POLICY_FILE)
utils.write_file(policy_file, policy)
mode_file = os.path.join(thermal_zone_folder, THERMAL_ZONE_MODE_FILE)
utils.write_file(mode_file, mode)
if THERMAL_DEV_CATEGORY_MODULE in cls.thermal_profile:
start, count = cls.thermal_profile[THERMAL_DEV_CATEGORY_MODULE]
if count != 0:
for index in range(count):
cls._write_generic_file(join(THERMAL_ZONE_MODULE_PATH.format(start + index), THERMAL_ZONE_MODE), content)
cls._write_generic_file(join(THERMAL_ZONE_MODULE_PATH.format(start + index), THERMAL_ZONE_POLICY), policy)
if THERMAL_DEV_CATEGORY_GEARBOX in cls.thermal_profile:
start, count = cls.thermal_profile[THERMAL_DEV_CATEGORY_GEARBOX]
if count != 0:
for index in range(count):
cls._write_generic_file(join(THERMAL_ZONE_GEARBOX_PATH.format(start + index), THERMAL_ZONE_MODE), content)
cls._write_generic_file(join(THERMAL_ZONE_GEARBOX_PATH.format(start + index), THERMAL_ZONE_POLICY), policy)
return True
@classmethod
@ -624,64 +361,91 @@ class Thermal(ThermalBase):
Returns:
True if all thermal zones current temperature less or equal than normal temperature
"""
if not cls.thermal_profile:
raise Exception("Fail to get thermal profile for this switch")
if not cls._check_thermal_zone_temperature(THERMAL_ZONE_ASIC_PATH):
return False
if THERMAL_DEV_CATEGORY_MODULE in cls.thermal_profile:
start, count = cls.thermal_profile[THERMAL_DEV_CATEGORY_MODULE]
if count != 0:
for index in range(count):
if not cls._check_thermal_zone_temperature(THERMAL_ZONE_MODULE_PATH.format(start + index)):
return False
if THERMAL_DEV_CATEGORY_GEARBOX in cls.thermal_profile:
start, count = cls.thermal_profile[THERMAL_DEV_CATEGORY_GEARBOX]
if count != 0:
for index in range(count):
if not cls._check_thermal_zone_temperature(THERMAL_ZONE_GEARBOX_PATH.format(start + index)):
for thermal_zone_folder in glob.iglob(THERMAL_ZONE_FOLDER_WILDCARD):
if not cls._check_thermal_zone_temperature(thermal_zone_folder):
return False
return True
@classmethod
def _check_thermal_zone_temperature(cls, thermal_zone_path):
normal_temp_path = join(thermal_zone_path, THERMAL_ZONE_NORMAL_TEMPERATURE)
current_temp_path = join(thermal_zone_path, THERMAL_ZONE_TEMPERATURE)
normal = None
current = None
threshold_path = os.path.join(thermal_zone_path, THERMAL_ZONE_THRESHOLD_FILE)
current_temp_path = os.path.join(thermal_zone_path, THERMAL_ZONE_TEMP_FILE)
try:
with open(normal_temp_path, 'r') as file_obj:
normal = float(file_obj.read())
with open(current_temp_path, 'r') as file_obj:
current = float(file_obj.read())
return current <= normal
threshold = utils.read_int_from_file(threshold_path, raise_exception=True)
current = utils.read_int_from_file(current_temp_path, raise_exception=True)
return current <= threshold
except Exception as e:
logger.log_info("Fail to check thermal zone temperature for file {} due to {}".format(thermal_zone_path, repr(e)))
return False
@classmethod
def check_module_temperature_trustable(cls):
if not cls.thermal_profile:
raise Exception("Fail to get thermal profile for this switch")
start, count = cls.thermal_profile[THERMAL_DEV_CATEGORY_MODULE]
for index in range(count):
fault_file_path = MODULE_TEMPERATURE_FAULT_PATH.format(index + start)
fault = cls._read_generic_file(fault_file_path, 0)
if fault.strip() != '0':
for file_path in glob.iglob(MODULE_TEMP_FAULT_WILDCARRD):
fault = utils.read_int_from_file(file_path)
if fault != 0:
return 'untrust'
return 'trust'
@classmethod
def get_min_amb_temperature(cls):
fan_ambient_path = join(HW_MGMT_THERMAL_ROOT, THERMAL_DEV_FAN_AMBIENT)
port_ambient_path = join(HW_MGMT_THERMAL_ROOT, THERMAL_DEV_PORT_AMBIENT)
fan_ambient_path = os.path.join(CHASSIS_THERMAL_SYSFS_FOLDER, 'fan_amb')
port_ambient_path = os.path.join(CHASSIS_THERMAL_SYSFS_FOLDER, 'port_amb')
# if there is any exception, let it raise
fan_ambient_temp = int(cls._read_generic_file(fan_ambient_path, 0))
port_ambient_temp = int(cls._read_generic_file(port_ambient_path, 0))
try:
fan_ambient_temp = utils.read_int_from_file(fan_ambient_path, raise_exception=True)
port_ambient_temp = utils.read_int_from_file(port_ambient_path, raise_exception=True)
return fan_ambient_temp if fan_ambient_temp < port_ambient_temp else port_ambient_temp
except Exception as e:
# Can't get ambient temperature, return maximum
logger.log_error('Failed to get minimum ambient temperature, use pessimistic instead')
return MAX_AMBIENT_TEMP
class RemovableThermal(Thermal):
def __init__(self, name, temp_file, high_th_file, high_crit_th_file, position, presence_cb):
super(RemovableThermal, self).__init__(name, temp_file, high_th_file, high_crit_th_file, position)
self.presence_cb = presence_cb
def get_temperature(self):
"""
Retrieves current temperature reading from thermal
Returns:
A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125
"""
status, hint = self.presence_cb()
if not status:
logger.log_debug("get_temperature for {} failed due to {}".format(self.name, hint))
return None
return super(RemovableThermal, self).get_temperature()
def get_high_threshold(self):
"""
Retrieves the high threshold temperature of thermal
Returns:
A float number, the high threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
status, hint = self.presence_cb()
if not status:
logger.log_debug("get_high_threshold for {} failed due to {}".format(self.name, hint))
return None
return super(RemovableThermal, self).get_high_threshold()
def get_high_critical_threshold(self):
"""
Retrieves the high critical threshold temperature of thermal
Returns:
A float number, the high critical threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
status, hint = self.presence_cb()
if not status:
logger.log_debug("get_high_critical_threshold for {} failed due to {}".format(self.name, hint))
return None
return super(RemovableThermal, self).get_high_critical_threshold()

View File

@ -166,19 +166,19 @@ class ThermalRecoverAction(ThermalPolicyActionBase):
class ChangeMinCoolingLevelAction(ThermalPolicyActionBase):
UNKNOWN_SKU_COOLING_LEVEL = 6
def execute(self, thermal_info_dict):
from .device_data import DEVICE_DATA
from .device_data import DeviceDataManager
from .fan import Fan
from .thermal_infos import ChassisInfo
from .thermal_conditions import MinCoolingLevelChangeCondition
from .thermal_conditions import UpdateCoolingLevelToMinCondition
chassis = thermal_info_dict[ChassisInfo.INFO_NAME].get_chassis()
if chassis.platform_name not in DEVICE_DATA or 'thermal' not in DEVICE_DATA[chassis.platform_name] or 'minimum_table' not in DEVICE_DATA[chassis.platform_name]['thermal']:
minimum_table = DeviceDataManager.get_minimum_table()
if not minimum_table:
Fan.min_cooling_level = ChangeMinCoolingLevelAction.UNKNOWN_SKU_COOLING_LEVEL
else:
trust_state = MinCoolingLevelChangeCondition.trust_state
temperature = MinCoolingLevelChangeCondition.temperature
minimum_table = DEVICE_DATA[chassis.platform_name]['thermal']['minimum_table']['unk_{}'.format(trust_state)]
minimum_table = minimum_table['unk_{}'.format(trust_state)]
for key, cooling_level in minimum_table.items():
temp_range = key.split(':')

View File

@ -16,52 +16,94 @@
#
import functools
import subprocess
from sonic_py_common.logger import Logger
# flags to indicate whether this process is running in docker or host
_is_host = None
logger = Logger()
def read_str_from_file(file_path, default='', raise_exception=False):
def read_from_file(file_path, target_type, default='', raise_exception=False, log_func=logger.log_error):
"""
Read content from file and convert to target type
:param file_path: File path
:param target_type: target type
:param default: Default return value if any exception occur
:param raise_exception: Raise exception to caller if True else just return default value
:param log_func: function to log the error
:return: String content of the file
"""
try:
with open(file_path, 'r') as f:
value = target_type(f.read().strip())
except (ValueError, IOError) as e:
if log_func:
log_func('Failed to read from file {} - {}'.format(file_path, repr(e)))
if not raise_exception:
value = default
else:
raise e
return value
def read_str_from_file(file_path, default='', raise_exception=False, log_func=logger.log_error):
"""
Read string content from file
:param file_path: File path
:param default: Default return value if any exception occur
:param raise_exception: Raise exception to caller if True else just return default value
:param log_func: function to log the error
:return: String content of the file
"""
try:
with open(file_path, 'r') as f:
value = f.read().strip()
except (ValueError, IOError) as e:
if not raise_exception:
value = default
else:
raise e
return value
return read_from_file(file_path=file_path, target_type=str, default=default, raise_exception=raise_exception, log_func=log_func)
def read_int_from_file(file_path, default=0, raise_exception=False):
def read_int_from_file(file_path, default=0, raise_exception=False, log_func=logger.log_error):
"""
Read content from file and cast it to integer
:param file_path: File path
:param default: Default return value if any exception occur
:param raise_exception: Raise exception to caller if True else just return default value
:param log_func: function to log the error
:return: Integer value of the file content
"""
try:
with open(file_path, 'r') as f:
value = int(f.read().strip())
except (ValueError, IOError) as e:
if not raise_exception:
value = default
else:
raise e
return value
return read_from_file(file_path=file_path, target_type=int, default=default, raise_exception=raise_exception, log_func=log_func)
def write_file(file_path, content, raise_exception=False):
def read_float_from_file(file_path, default=0.0, raise_exception=False, log_func=logger.log_error):
"""
Read content from file and cast it to integer
:param file_path: File path
:param default: Default return value if any exception occur
:param raise_exception: Raise exception to caller if True else just return default value
:param log_func: function to log the error
:return: Integer value of the file content
"""
return read_from_file(file_path=file_path, target_type=float, default=default, raise_exception=raise_exception, log_func=log_func)
def _key_value_converter(content):
ret = {}
for line in content.splitlines():
k,v = line.split(':')
ret[k.strip()] = v.strip()
return ret
def read_key_value_file(file_path, default={}, raise_exception=False, log_func=logger.log_error):
"""Read file content and parse the content to a dict. The file content should like:
key1:value1
key2:value2
Args:
file_path (str): file path
default (dict, optional): default return value. Defaults to {}.
raise_exception (bool, optional): If exception should be raised or hiden. Defaults to False.
log_func (optional): logger function.. Defaults to logger.log_error.
"""
return read_from_file(file_path=file_path, target_type=_key_value_converter, default=default, raise_exception=raise_exception, log_func=log_func)
def write_file(file_path, content, raise_exception=False, log_func=logger.log_error):
"""
Write the given value to a file
:param file_path: File path
@ -73,6 +115,8 @@ def write_file(file_path, content, raise_exception=False):
with open(file_path, 'w') as f:
f.write(str(content))
except (ValueError, IOError) as e:
if log_func:
log_func('Failed to write {} to file {} - {}'.format(content, file_path, repr(e)))
if not raise_exception:
return False
else:
@ -80,16 +124,50 @@ def write_file(file_path, content, raise_exception=False):
return True
def pre_initialize(init_func):
def decorator(method):
@functools.wraps(method)
def _impl(self, *args, **kwargs):
init_func(self)
return method(self, *args, **kwargs)
return _impl
return decorator
def pre_initialize_one(init_func):
def decorator(method):
@functools.wraps(method)
def _impl(self, index):
init_func(self, index)
return method(self, index)
return _impl
return decorator
def read_only_cache():
"""Decorator to cache return value for a method/function once.
This decorator should be used for method/function when:
1. Executing the method/function takes time. e.g. reading sysfs.
2. The return value of this method/function never changes.
"""
def decorator(method):
method.return_value = None
@functools.wraps(method)
def _impl(*args, **kwargs):
if not method.return_value:
method.return_value = method(*args, **kwargs)
return method.return_value
return _impl
return decorator
@read_only_cache()
def is_host():
"""
Test whether current process is running on the host or an docker
return True for host and False for docker
"""
global _is_host
if _is_host is not None:
return _is_host
_is_host = False
try:
proc = subprocess.Popen("docker --version 2>/dev/null",
stdout=subprocess.PIPE,
@ -99,22 +177,20 @@ def is_host():
stdout = proc.communicate()[0]
proc.wait()
result = stdout.rstrip('\n')
if result != '':
_is_host = True
return result != ''
except OSError as e:
pass
return _is_host
return False
def default_return(return_value):
def default_return(return_value, log_func=logger.log_debug):
def wrapper(method):
@functools.wraps(method)
def _impl(*args, **kwargs):
try:
return method(*args, **kwargs)
except:
except Exception as e:
if log_func:
log_func('Faield to execute method {} - {}'.format(method.__name__, repr(e)))
return return_value
return _impl
return wrapper

View File

@ -0,0 +1,84 @@
#
# 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 os
from sonic_py_common.logger import Logger
from . import utils
logger = Logger()
SN_VPD_FIELD = "SN_VPD_FIELD"
PN_VPD_FIELD = "PN_VPD_FIELD"
REV_VPD_FIELD = "REV_VPD_FIELD"
class VpdParser:
def __init__(self, file_path):
self.vpd_data = {}
self.vpd_file = file_path
self.vpd_file_last_mtime = None
def _get_data(self):
if not os.path.exists(self.vpd_file):
self.vpd_data = {}
return False
try:
mtime = os.stat(self.vpd_file).st_mtime
if mtime != self.vpd_file_last_mtime:
self.vpd_file_last_mtime = mtime
self.vpd_data = utils.read_key_value_file(self.vpd_file)
return True
except Exception as e:
self.vpd_data = {}
return False
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
if self._get_data() and PN_VPD_FIELD not in self.vpd_data:
logger.log_error("Fail to read model number: No key {} in VPD {}".format(PN_VPD_FIELD, self.vpd_file))
return 'N/A'
return self.vpd_data.get(PN_VPD_FIELD, 'N/A')
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
if self._get_data() and SN_VPD_FIELD not in self.vpd_data:
logger.log_error("Fail to read serial number: No key {} in VPD {}".format(SN_VPD_FIELD, self.vpd_file))
return 'N/A'
return self.vpd_data.get(SN_VPD_FIELD, 'N/A')
def get_revision(self):
"""
Retrieves the hardware revision of the device
Returns:
string: Revision value of device
"""
if self._get_data() and REV_VPD_FIELD not in self.vpd_data:
logger.log_error("Fail to read revision: No key {} in VPD {}".format(REV_VPD_FIELD, self.vpd_file))
return 'N/A'
return self.vpd_data.get(REV_VPD_FIELD, 'N/A')

View File

@ -0,0 +1,44 @@
#
# 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 os
import pytest
import sys
test_path = os.path.dirname(os.path.abspath(__file__))
modules_path = os.path.dirname(test_path)
sys.path.insert(0, modules_path)
os.environ["PLATFORM_API_UNIT_TESTING"] = "1"
from sonic_platform import utils
@pytest.fixture(scope='function', autouse=True)
def auto_recover_mock():
"""Auto used fixture to recover some critical mocked functions
"""
origin_os_path_exists = os.path.exists
origin_read_int_from_file = utils.read_int_from_file
origin_read_str_from_file = utils.read_str_from_file
origin_read_float_from_file = utils.read_float_from_file
origin_write_file = utils.write_file
yield
os.path.exists = origin_os_path_exists
utils.read_int_from_file = origin_read_int_from_file
utils.read_str_from_file = origin_read_str_from_file
utils.write_file = origin_write_file
utils.read_float_from_file = origin_read_float_from_file

View File

@ -0,0 +1,10 @@
SN_VPD_FIELD: MT1946X07684
EFT_REV: 1
PN_VPD_FIELD: MTEF-PSF-AC-C
REV_VPD_FIELD: A3
MFG_DATE_FIELD: 1B94BF
MFR_NAME: DELTA
FEED: AC/DC
CAPACITY: 1100
MAX_RPM:23000
MIN_RPM:4600

View File

@ -0,0 +1,271 @@
#
# 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 os
import sys
from mock import MagicMock
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 TestChassis:
"""Test class to test chassis.py. The test cases covers:
1. PSU related API
2. Fan drawer related API
3. SFP related API (Except modular chassis SFP related API)
4. Reboot cause related API
Thermal, Eeprom, Watchdog, Component, System LED related API will be tested in seperate class
"""
@classmethod
def setup_class(cls):
os.environ["MLNX_PLATFORM_API_UNIT_TESTING"] = "1"
def test_psu(self):
from sonic_platform.psu import Psu, FixedPsu
# Test creating hot swapable PSU
DeviceDataManager.get_psu_count = mock.MagicMock(return_value=2)
DeviceDataManager.is_psu_hotswapable = mock.MagicMock(return_value=True)
chassis = Chassis()
chassis.initialize_psu()
assert len(chassis._psu_list) == 2
assert len(list(filter(lambda x: isinstance(x, Psu) ,chassis._psu_list))) == 2
# Test creating fixed PSU
DeviceDataManager.get_psu_count = mock.MagicMock(return_value=3)
DeviceDataManager.is_psu_hotswapable = mock.MagicMock(return_value=False)
chassis._psu_list = []
chassis.initialize_psu()
assert len(chassis._psu_list) == 3
assert len(list(filter(lambda x: isinstance(x, FixedPsu) ,chassis._psu_list))) == 3
# Test chassis.get_all_psus
chassis._psu_list = []
psu_list = chassis.get_all_psus()
assert len(psu_list) == 3
# Test chassis.get_psu
chassis._psu_list = []
psu = chassis.get_psu(0)
assert psu and isinstance(psu, FixedPsu)
psu = chassis.get_psu(3)
assert psu is None
# Test chassis.get_num_psus
chassis._psu_list = []
assert chassis.get_num_psus() == 3
def test_fan(self):
from sonic_platform.fan_drawer import RealDrawer, VirtualDrawer
# Test creating fixed fan
DeviceDataManager.is_fan_hotswapable = mock.MagicMock(return_value=False)
assert DeviceDataManager.get_fan_drawer_count() == 1
DeviceDataManager.get_fan_count = mock.MagicMock(return_value=4)
chassis = Chassis()
chassis.initialize_fan()
assert len(chassis._fan_drawer_list) == 1
assert len(list(filter(lambda x: isinstance(x, VirtualDrawer) ,chassis._fan_drawer_list))) == 1
assert chassis.get_fan_drawer(0).get_num_fans() == 4
# Test creating hot swapable fan
DeviceDataManager.get_fan_drawer_count = mock.MagicMock(return_value=2)
DeviceDataManager.get_fan_count = mock.MagicMock(return_value=4)
DeviceDataManager.is_fan_hotswapable = mock.MagicMock(return_value=True)
chassis._fan_drawer_list = []
chassis.initialize_fan()
assert len(chassis._fan_drawer_list) == 2
assert len(list(filter(lambda x: isinstance(x, RealDrawer) ,chassis._fan_drawer_list))) == 2
assert chassis.get_fan_drawer(0).get_num_fans() == 2
assert chassis.get_fan_drawer(1).get_num_fans() == 2
# Test chassis.get_all_fan_drawers
chassis._fan_drawer_list = []
assert len(chassis.get_all_fan_drawers()) == 2
# Test chassis.get_fan_drawer
chassis._fan_drawer_list = []
fan_drawer = chassis.get_fan_drawer(0)
assert fan_drawer and isinstance(fan_drawer, RealDrawer)
fan_drawer = chassis.get_fan_drawer(2)
assert fan_drawer is None
# Test chassis.get_num_fan_drawers
chassis._fan_drawer_list = []
assert chassis.get_num_fan_drawers() == 2
def test_sfp(self):
# Test get_num_sfps, it should not create any SFP objects
DeviceDataManager.get_sfp_count = mock.MagicMock(return_value=3)
chassis = Chassis()
assert chassis.get_num_sfps() == 3
assert len(chassis._sfp_list) == 0
# Index out of bound, return None
sfp = chassis.get_sfp(4)
assert sfp is None
assert len(chassis._sfp_list) == 0
# Get one SFP, other SFP list should be initialized to None
sfp = chassis.get_sfp(1)
assert sfp is not None
assert len(chassis._sfp_list) == 3
assert chassis._sfp_list[1] is None
assert chassis._sfp_list[2] is None
assert chassis.sfp_initialized_count == 1
# Get the SFP again, no new SFP created
sfp1 = chassis.get_sfp(1)
assert id(sfp) == id(sfp1)
# Get another SFP, sfp_initialized_count increase
sfp2 = chassis.get_sfp(2)
assert sfp2 is not None
assert chassis._sfp_list[2] is None
assert chassis.sfp_initialized_count == 2
# Get all SFPs, but there are SFP already created, only None SFP created
sfp_list = chassis.get_all_sfps()
assert len(sfp_list) == 3
assert chassis.sfp_initialized_count == 3
assert list(filter(lambda x: x is not None, sfp_list))
assert id(sfp1) == id(sfp_list[0])
assert id(sfp2) == id(sfp_list[1])
# Get all SFPs, no SFP yet, all SFP created
chassis._sfp_list = []
chassis.sfp_initialized_count = 0
sfp_list = chassis.get_all_sfps()
assert len(sfp_list) == 3
assert chassis.sfp_initialized_count == 3
@mock.patch('sonic_platform.sfp_event.sfp_event.check_sfp_status', MagicMock())
@mock.patch('sonic_platform.sfp_event.sfp_event.__init__', MagicMock(return_value=None))
@mock.patch('sonic_platform.sfp_event.sfp_event.initialize', MagicMock())
@mock.patch('sonic_platform.sfp.SFP.reinit', MagicMock())
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_sfp_count', MagicMock(return_value=3))
def test_change_event(self):
from sonic_platform.sfp_event import sfp_event
from sonic_platform.sfp import SFP
return_port_dict = {1: '1'}
def mock_check_sfp_status(self, port_dict, error_dict, timeout):
port_dict.update(return_port_dict)
return True if port_dict else False
sfp_event.check_sfp_status = mock_check_sfp_status
chassis = Chassis()
# Call get_change_event with timeout=0, wait until an event is detected
status, event_dict = chassis.get_change_event()
assert status is True
assert 'sfp' in event_dict and event_dict['sfp'][1] == '1'
assert len(chassis._sfp_list) == 3
assert SFP.reinit.call_count == 1
# Call get_change_event with timeout=1.0
return_port_dict = {}
status, event_dict = chassis.get_change_event(timeout=1.0)
assert status is True
assert 'sfp' in event_dict and not event_dict['sfp']
def test_reboot_cause(self):
from sonic_platform import utils
from sonic_platform.chassis import REBOOT_CAUSE_ROOT
chassis = Chassis()
major, minor = chassis.get_reboot_cause()
assert major == chassis.REBOOT_CAUSE_NON_HARDWARE
assert minor == ''
mock_file_content = {}
def read_int_from_file(file_path, *args, **kwargs):
return mock_file_content[file_path]
utils.read_int_from_file = read_int_from_file
for key, value in chassis.reboot_major_cause_dict.items():
file_path = os.path.join(REBOOT_CAUSE_ROOT, key)
mock_file_content[file_path] = 1
major, minor = chassis.get_reboot_cause()
assert major == value
assert minor == ''
mock_file_content[file_path] = 0
for key, value in chassis.reboot_minor_cause_dict.items():
file_path = os.path.join(REBOOT_CAUSE_ROOT, key)
mock_file_content[file_path] = 1
major, minor = chassis.get_reboot_cause()
assert major == chassis.REBOOT_CAUSE_HARDWARE_OTHER
assert minor == value
mock_file_content[file_path] = 0
def test_module(self):
from sonic_platform.chassis import ModularChassis
# Test get_num_modules, it should not create any SFP objects
DeviceDataManager.get_linecard_count = mock.MagicMock(return_value=3)
chassis = ModularChassis()
assert chassis.is_modular_chassis()
assert chassis.get_num_modules() == 3
assert len(chassis._module_list) == 0
# Index out of bound, return None
m = chassis.get_module(3)
assert m is None
assert len(chassis._module_list) == 0
# Get one Module, other Module in list should be initialized to None
m = chassis.get_module(0)
assert m is not None
assert len(chassis._module_list) == 3
assert chassis._module_list[1] is None
assert chassis._module_list[2] is None
assert chassis.module_initialized_count == 1
# Get the Module again, no new Module created
m1 = chassis.get_module(0)
assert id(m) == id(m1)
# Get another Module, module_initialized_count increase
m2 = chassis.get_module(1)
assert m2 is not None
assert chassis._module_list[2] is None
assert chassis.module_initialized_count == 2
# Get all SFPs, but there are SFP already created, only None SFP created
module_list = chassis.get_all_modules()
assert len(module_list) == 3
assert chassis.module_initialized_count == 3
assert list(filter(lambda x: x is not None, module_list))
assert id(m1) == id(module_list[0])
assert id(m2) == id(module_list[1])
# Get all SFPs, no SFP yet, all SFP created
chassis._module_list = []
chassis.module_initialized_count = 0
module_list = chassis.get_all_modules()
assert len(module_list) == 3
assert chassis.module_initialized_count == 3

View File

@ -0,0 +1,108 @@
#
# 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 os
import pytest
import sys
if sys.version_info.major == 3:
from unittest.mock import MagicMock, patch
else:
from mock import MagicMock, patch
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.eeprom import Eeprom, EepromContentVisitor
class TestEeprom:
@patch('os.path.exists', MagicMock(return_value=True))
@patch('os.path.islink', MagicMock(return_value=True))
@patch('sonic_platform.eeprom.Eeprom.get_system_eeprom_info')
def test_chassis_eeprom(self, mock_eeprom_info):
mock_eeprom_info.return_value = {
hex(Eeprom._TLV_CODE_PRODUCT_NAME): 'MSN3420',
hex(Eeprom._TLV_CODE_PART_NUMBER): 'MSN3420-CB2FO',
hex(Eeprom._TLV_CODE_MAC_BASE): '1C:34:DA:1C:9F:00',
hex(Eeprom._TLV_CODE_SERIAL_NUMBER): 'MT2019X13878'
}
chassis = Chassis()
assert chassis.get_name() == 'MSN3420'
assert chassis.get_model() == 'MSN3420-CB2FO'
assert chassis.get_base_mac() == '1C:34:DA:1C:9F:00'
assert chassis.get_serial() == 'MT2019X13878'
assert chassis.get_system_eeprom_info() == mock_eeprom_info.return_value
def test_eeprom_init(self):
# Test symlink not exist, there is an exception
with pytest.raises(RuntimeError):
Eeprom()
@patch('os.path.exists', MagicMock(return_value=True))
@patch('os.path.islink', MagicMock(return_value=True))
def test_get_system_eeprom_info_from_db(self):
return_values = {
('EEPROM_INFO|State', 'Initialized'): '1',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_PRODUCT_NAME)), 'Value'): 'MSN3420',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_PART_NUMBER)), 'Value'): 'MSN3420-CB2FO',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_MAC_BASE)), 'Value'): '1C:34:DA:1C:9F:00',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_SERIAL_NUMBER)), 'Value'): 'MT2019X13878',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_VENDOR_EXT)), 'Num_vendor_ext'): '2',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_VENDOR_EXT)), 'Value_0'): 'ext1',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_VENDOR_EXT)), 'Value_1'): 'ext2',
('EEPROM_INFO|{}'.format(hex(Eeprom._TLV_CODE_CRC_32)), 'Value'): 'CRC_VALUE',
}
def side_effect(key, field):
return return_values.get((key, field))
eeprom = Eeprom()
eeprom._redis_hget = MagicMock(side_effect = side_effect)
info = eeprom.get_system_eeprom_info()
assert eeprom.get_product_name() == 'MSN3420'
assert eeprom.get_part_number() == 'MSN3420-CB2FO'
assert eeprom.get_base_mac() == '1C:34:DA:1C:9F:00'
assert eeprom.get_serial_number() == 'MT2019X13878'
assert info[hex(Eeprom._TLV_CODE_VENDOR_EXT)] == ['ext1', 'ext2']
assert info[hex(Eeprom._TLV_CODE_CRC_32)] == 'CRC_VALUE'
@patch('os.path.exists', MagicMock(return_value=True))
@patch('os.path.islink', MagicMock(return_value=True))
def test_get_system_eeprom_info_from_hardware(self):
eeprom = Eeprom()
eeprom.p = os.path.join(test_path, 'mock_eeprom_data')
eeprom._redis_hget = MagicMock()
info = eeprom.get_system_eeprom_info()
assert eeprom.get_product_name() == 'MSN3800'
assert eeprom.get_part_number() == 'MSN3800-CS2FO'
assert eeprom.get_base_mac() == 'B8:59:9F:A9:34:00'
assert eeprom.get_serial_number() == 'MT1937X00537'
assert info[hex(Eeprom._TLV_CODE_CRC_32)] == '0x9EFF0119'
def test_eeprom_content_visitor(self):
content = {}
v = EepromContentVisitor(content)
v.visit_tlv('tlv1', Eeprom._TLV_CODE_PRODUCT_NAME, 7, 'MSN3420')
v.visit_tlv('tlv2', Eeprom._TLV_CODE_VENDOR_EXT, 4, 'ext1')
v.visit_tlv('tlv3', Eeprom._TLV_CODE_VENDOR_EXT, 4, 'ext2')
assert content[hex(Eeprom._TLV_CODE_PRODUCT_NAME)] == 'MSN3420'
assert content[hex(Eeprom._TLV_CODE_VENDOR_EXT)] == ['ext1', 'ext2']

View File

@ -15,59 +15,170 @@
# limitations under the License.
#
import os
import sys
import pytest
from mock import MagicMock
from .mock_platform import MockFan
import subprocess
import sys
from mock import call, MagicMock
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.fan import Fan
from sonic_platform.led import FanLed
from sonic_platform.fan_drawer import RealDrawer
from sonic_platform.device_data import DEVICE_DATA
from sonic_platform import utils
from sonic_platform.fan import Fan, PsuFan, COOLING_STATE_PATH
from sonic_platform.fan_drawer import RealDrawer, VirtualDrawer
from sonic_platform.psu import Psu
def test_get_absence_fan_direction():
fan_drawer = RealDrawer(0, DEVICE_DATA['x86_64-mlnx_msn2700-r0']['fans'])
fan = Fan(0, fan_drawer, 1)
class TestFan:
def test_fan_drawer_basic(self):
# Real drawer
fan_drawer = RealDrawer(0)
assert fan_drawer.get_index() == 1
assert fan_drawer.get_name() == 'drawer1'
utils.read_int_from_file = MagicMock(return_value=1)
assert fan_drawer.get_presence() is True
utils.read_int_from_file = MagicMock(return_value=0)
assert fan_drawer.get_presence() is False
assert fan_drawer.get_position_in_parent() == 1
assert fan_drawer.is_replaceable() is True
fan_drawer.get_presence = MagicMock(return_value=False)
assert fan_drawer.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE
fan_drawer.get_presence = MagicMock(return_value=True)
assert fan_drawer.get_direction() == Fan.FAN_DIRECTION_EXHAUST
utils.read_int_from_file = MagicMock(return_value=1)
assert fan_drawer.get_direction() == Fan.FAN_DIRECTION_INTAKE
# Invalid fan dir value
utils.read_int_from_file = MagicMock(return_value=2)
assert fan_drawer.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE
assert not fan.is_psu_fan
utils.read_int_from_file = MagicMock(side_effect=ValueError(''))
assert fan_drawer.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE
utils.read_int_from_file = MagicMock(side_effect=IOError(''))
assert fan_drawer.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE
# Virtual drawer
fan_drawer = VirtualDrawer(0)
assert fan_drawer.get_name() == 'N/A'
assert fan_drawer.get_presence() is True
assert fan_drawer.is_replaceable() is False
def test_system_fan_basic(self):
fan_drawer = RealDrawer(0)
fan = Fan(2, fan_drawer, 1)
assert fan.get_position_in_parent() == 1
assert fan.is_replaceable() is False
assert fan.get_speed_tolerance() == 50
assert fan.get_name() == 'fan3'
mock_sysfs_content = {
fan.fan_speed_get_path: 50,
fan.fan_max_speed_path: 100,
fan.fan_status_path: 0,
fan.fan_speed_set_path: 153
}
def mock_read_int_from_file(file_path, default=0, raise_exception=False):
return mock_sysfs_content[file_path]
utils.read_int_from_file = mock_read_int_from_file
assert fan.get_speed() == 50
mock_sysfs_content[fan.fan_speed_get_path] = 101
assert fan.get_speed() == 100
mock_sysfs_content[fan.fan_max_speed_path] = 0
assert fan.get_speed() == 101
assert fan.get_status() is True
mock_sysfs_content[fan.fan_status_path] = 1
assert fan.get_status() is False
assert fan.get_target_speed() == 60
fan.fan_drawer.get_direction = MagicMock(return_value=Fan.FAN_DIRECTION_EXHAUST)
assert fan.get_direction() == Fan.FAN_DIRECTION_EXHAUST
fan.fan_drawer.get_presence = MagicMock(return_value=True)
assert fan.get_presence() is True
def test_system_fan_set_speed(self):
fan_drawer = RealDrawer(0)
fan = Fan(2, fan_drawer, 1)
fan.min_cooling_level = 2
fan.set_cooling_level = MagicMock()
utils.write_file = MagicMock()
fan.set_speed(60)
fan.set_cooling_level.assert_called_with(6, 6)
utils.write_file.assert_called_with(fan.fan_speed_set_path, 153, raise_exception=True)
fan.min_cooling_level = 7
fan.set_speed(60)
fan.set_cooling_level.assert_called_with(7, 7)
utils.write_file.assert_called_with(fan.fan_speed_set_path, 178, raise_exception=True)
def test_set_cooling_level(self):
with pytest.raises(RuntimeError):
Fan.set_cooling_level(11, 11)
utils.write_file = MagicMock()
Fan.set_cooling_level(10, 10)
calls = [call(COOLING_STATE_PATH, 20, raise_exception=True), call(COOLING_STATE_PATH, 10, raise_exception=True)]
utils.write_file.assert_has_calls(calls)
utils.write_file = MagicMock(side_effect=IOError(''))
with pytest.raises(RuntimeError):
Fan.set_cooling_level(10, 10)
utils.write_file = MagicMock(side_effect=ValueError(''))
with pytest.raises(RuntimeError):
Fan.set_cooling_level(10, 10)
def test_get_cooling_level(self):
utils.read_int_from_file = MagicMock()
Fan.get_cooling_level()
utils.read_int_from_file.assert_called_with(COOLING_STATE_PATH, raise_exception=True)
utils.read_int_from_file = MagicMock(side_effect=IOError(''))
with pytest.raises(RuntimeError):
Fan.get_cooling_level()
utils.read_int_from_file = MagicMock(side_effect=ValueError(''))
with pytest.raises(RuntimeError):
Fan.get_cooling_level()
def test_psu_fan_basic(self):
psu = Psu(0)
fan = PsuFan(0, 1, psu)
assert fan.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE
assert fan.get_status() is True
assert fan.get_presence() is False
psu.get_presence = MagicMock(return_value=True)
assert fan.get_presence() is False
psu.get_powergood_status = MagicMock(return_value=True)
assert fan.get_presence() is False
os.path.exists = MagicMock(return_value=True)
assert fan.get_presence() is True
fan.get_cooling_level = MagicMock(return_value=7)
assert fan.get_target_speed() == 70
def test_fan_drawer_set_status_led():
fan_drawer = RealDrawer(0, DEVICE_DATA['x86_64-mlnx_msn2700-r0']['fans'])
with pytest.raises(Exception):
fan_drawer.set_status_led(None, 'Invalid color')
with pytest.raises(Exception):
fan_drawer.set_status_led(None, Fan.STATUS_LED_COLOR_RED)
fan1 = Fan(0, fan_drawer, 1)
fan2 = Fan(1, fan_drawer, 2)
fan_list = fan_drawer.get_all_fans()
fan_list.append(fan1)
fan_list.append(fan2)
FanLed.set_status = MagicMock()
fan1.set_status_led(Fan.STATUS_LED_COLOR_RED)
fan_drawer.set_status_led(Fan.STATUS_LED_COLOR_RED)
FanLed.set_status.assert_called_with(Fan.STATUS_LED_COLOR_RED)
fan2.set_status_led(Fan.STATUS_LED_COLOR_GREEN)
fan_drawer.set_status_led(Fan.STATUS_LED_COLOR_GREEN)
FanLed.set_status.assert_called_with(Fan.STATUS_LED_COLOR_RED)
fan1.set_status_led(Fan.STATUS_LED_COLOR_GREEN)
fan_drawer.set_status_led(Fan.STATUS_LED_COLOR_GREEN)
FanLed.set_status.assert_called_with(Fan.STATUS_LED_COLOR_GREEN)
fan1.set_status_led(Fan.STATUS_LED_COLOR_RED)
fan_drawer.set_status_led(Fan.STATUS_LED_COLOR_RED)
FanLed.set_status.assert_called_with(Fan.STATUS_LED_COLOR_RED)
def test_psu_fan_set_speed(self):
psu = Psu(0)
fan = PsuFan(0, 1, psu)
subprocess.check_call = MagicMock()
mock_file_content = {
fan.psu_i2c_bus_path: 'bus',
fan.psu_i2c_addr_path: 'addr',
fan.psu_i2c_command_path: 'command'
}
def mock_read_str_from_file(file_path, default='', raise_exception=False):
return mock_file_content[file_path]
utils.read_str_from_file = mock_read_str_from_file
fan.set_speed(60)
assert subprocess.check_call.call_count == 0
fan.get_presence = MagicMock(return_value=True)
assert fan.set_speed(60)
subprocess.check_call.assert_called_with("i2cset -f -y {0} {1} {2} {3} wp".format('bus', 'addr', 'command', hex(60)), shell=True, universal_newlines=True)
subprocess.check_call = MagicMock(side_effect=subprocess.CalledProcessError('', ''))
assert not fan.set_speed(60)
subprocess.check_call = MagicMock()
utils.read_str_from_file = MagicMock(side_effect=RuntimeError(''))
assert not fan.set_speed(60)

View File

@ -0,0 +1,145 @@
#
# 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 os
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 import utils
from sonic_platform.chassis import Chassis
from sonic_platform.fan import Fan
from sonic_platform.fan_drawer import RealDrawer, VirtualDrawer
from sonic_platform.led import Led
from sonic_platform.psu import FixedPsu, Psu
class TestLed:
def test_chassis_led(self):
chassis = Chassis()
assert chassis._led is None
assert chassis.set_status_led('red') is False
physical_led = chassis._led
assert physical_led is not None
self._verify_non_shared_led(physical_led, chassis)
def _verify_non_shared_led(self, physical_led, obj):
mock_file_content = self._mock_led_file_content(physical_led)
def mock_read_str_from_file(file_path, **kwargs):
return mock_file_content[file_path]
def mock_write_file(file_path, content, **kwargs):
mock_file_content[file_path] = content
utils.read_str_from_file = mock_read_str_from_file
utils.write_file = mock_write_file
assert obj.get_status_led() == Led.STATUS_LED_COLOR_GREEN
mock_file_content[physical_led.get_green_led_path()] = Led.LED_OFF
assert obj.set_status_led(Led.STATUS_LED_COLOR_RED) is True
assert obj.get_status_led() == Led.STATUS_LED_COLOR_RED
mock_file_content[physical_led.get_red_led_path()] = Led.LED_OFF
assert obj.set_status_led(Led.STATUS_LED_COLOR_GREEN) is True
assert obj.get_status_led() == Led.STATUS_LED_COLOR_GREEN
mock_file_content[physical_led.get_green_led_path()] = Led.LED_OFF
assert obj.set_status_led(Led.STATUS_LED_COLOR_ORANGE) is False
assert obj.set_status_led(Led.STATUS_LED_COLOR_RED_BLINK)
assert obj.get_status_led() == Led.STATUS_LED_COLOR_RED_BLINK
mock_file_content[physical_led.get_red_led_delay_off_path()] = Led.LED_OFF
mock_file_content[physical_led.get_red_led_delay_on_path()] = Led.LED_OFF
assert obj.set_status_led(Led.STATUS_LED_COLOR_GREEN_BLINK)
assert obj.get_status_led() == Led.STATUS_LED_COLOR_GREEN_BLINK
mock_file_content[physical_led.get_green_led_delay_off_path()] = Led.LED_OFF
mock_file_content[physical_led.get_green_led_delay_on_path()] = Led.LED_OFF
assert obj.set_status_led(Led.STATUS_LED_COLOR_ORANGE_BLINK)
assert obj.get_status_led() == Led.STATUS_LED_COLOR_RED_BLINK
mock_file_content[physical_led.get_green_led_delay_off_path()] = Led.LED_OFF
mock_file_content[physical_led.get_green_led_delay_on_path()] = Led.LED_OFF
def _mock_led_file_content(self, led):
return {
led.get_green_led_path(): Led.LED_ON,
led.get_red_led_path(): Led.LED_OFF,
led.get_orange_led_path(): Led.LED_OFF,
led.get_led_cap_path(): 'none green green_blink red red_blink',
led.get_green_led_delay_off_path(): Led.LED_OFF,
led.get_green_led_delay_on_path(): Led.LED_OFF,
led.get_red_led_delay_off_path(): Led.LED_OFF,
led.get_red_led_delay_on_path(): Led.LED_OFF,
led.get_orange_led_delay_off_path(): Led.LED_OFF,
led.get_orange_led_delay_on_path(): Led.LED_OFF,
}
def test_fan_led(self):
fan_drawer = RealDrawer(0)
self._verify_fan_led(fan_drawer)
fan_drawer = VirtualDrawer(0)
self._verify_fan_led(fan_drawer)
def _verify_fan_led(self, fan_drawer):
fan1 = Fan(0, fan_drawer, 1)
fan2 = Fan(1, fan_drawer, 2)
physical_led = fan_drawer.get_led()._led
self._verify_shared_led(physical_led, fan1, fan2)
def _verify_shared_led(self, physical_led, obj1, obj2):
mock_file_content = self._mock_led_file_content(physical_led)
def mock_read_str_from_file(file_path, **kwargs):
return mock_file_content[file_path]
def mock_write_file(file_path, content, **kwargs):
mock_file_content[file_path] = content
utils.read_str_from_file = mock_read_str_from_file
utils.write_file = mock_write_file
assert obj1.set_status_led(Led.STATUS_LED_COLOR_GREEN)
assert obj2.get_status_led() == Led.STATUS_LED_COLOR_GREEN
mock_file_content[physical_led.get_green_led_path()] = Led.LED_OFF
assert obj2.set_status_led(Led.STATUS_LED_COLOR_RED)
assert obj2.get_status_led() == Led.STATUS_LED_COLOR_RED
assert obj1.set_status_led(Led.STATUS_LED_COLOR_RED)
assert obj2.get_status_led() == Led.STATUS_LED_COLOR_RED
mock_file_content[physical_led.get_red_led_path()] = Led.LED_OFF
assert obj1.set_status_led(Led.STATUS_LED_COLOR_GREEN)
assert obj1.get_status_led() == Led.STATUS_LED_COLOR_RED
assert obj2.get_status_led() == Led.STATUS_LED_COLOR_RED
assert obj2.set_status_led(Led.STATUS_LED_COLOR_GREEN)
assert obj1.get_status_led() == Led.STATUS_LED_COLOR_GREEN
assert obj1.get_status_led() == Led.STATUS_LED_COLOR_GREEN
def test_psu_led(self):
psu1 = Psu(0)
psu2 = Psu(1)
physical_led = Psu.get_shared_led()._led
self._verify_shared_led(physical_led, psu1, psu2)
def test_fixed_psu_led(self):
psu = FixedPsu(0)
physical_led = psu.led
self._verify_non_shared_led(physical_led, psu)

View File

@ -0,0 +1,185 @@
#
# 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 os
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 import utils
from sonic_platform.chassis import ModularChassis
from sonic_platform.device_data import DeviceDataManager
from sonic_platform.module import Module
class TestModule:
@classmethod
def setup_class(cls):
DeviceDataManager.get_linecard_sfp_count = mock.MagicMock(return_value=2)
DeviceDataManager.get_linecard_count = mock.MagicMock(return_value=2)
def test_chassis_get_num_sfp(self):
chassis = ModularChassis()
assert chassis.get_num_sfps() == 4
def test_chassis_get_all_sfps(self):
utils.read_int_from_file = mock.MagicMock(return_value=1)
chassis = ModularChassis()
assert len(chassis.get_all_sfps()) == 4
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_linecard_max_port_count', mock.MagicMock(return_value=16))
def test_chassis_get_sfp(self):
utils.read_int_from_file = mock.MagicMock(return_value=1)
index = 1
chassis = ModularChassis()
sfp = chassis.get_sfp(index)
assert sfp
def test_thermal(self):
from sonic_platform.thermal import THERMAL_NAMING_RULE
DeviceDataManager.get_gearbox_count = mock.MagicMock(return_value=2)
utils.read_int_from_file = mock.MagicMock(return_value=1)
m = Module(1)
assert m.get_num_thermals() == 2
assert len(m._thermal_list) == 0
thermals = m.get_all_thermals()
assert len(thermals) == 2
rule = THERMAL_NAMING_RULE['linecard thermals']
start_index = rule.get('start_index', 1)
for i, thermal in enumerate(thermals):
assert rule['name'].format(i + start_index) in thermal.get_name()
assert rule['temperature'].format(i + start_index) in thermal.temperature
assert rule['high_threshold'].format(i + start_index) in thermal.high_threshold
assert rule['high_critical_threshold'].format(i + start_index) in thermal.high_critical_threshold
assert thermal.get_position_in_parent() == i + 1
thermal = m.get_thermal(1)
assert thermal
assert thermal.get_position_in_parent() == 2
def get_sfp(self):
DeviceDataManager.get_linecard_sfp_count = mock.MagicMock(return_value=3)
utils.read_int_from_file = mock.MagicMock(return_value=1)
# Test get_num_sfps, it should not create any SFP objects
m = Module(1)
assert m.get_num_sfps() == 3
assert len(m._sfp_list) == 0
# Index out of bound, return None
sfp = m.get_sfp(3)
assert sfp is None
assert len(m._sfp_list) == 0
# Get one SFP, other SFP list should be initialized to None
sfp = m.get_sfp(0)
assert sfp is not None
assert len(m._sfp_list) == 3
assert m._sfp_list[1] is None
assert m._sfp_list[2] is None
assert m.sfp_initialized_count == 1
# Get the SFP again, no new SFP created
sfp1 = m.get_sfp(0)
assert id(sfp) == id(sfp1)
# Get another SFP, sfp_initialized_count increase
sfp2 = m.get_sfp(1)
assert sfp2 is not None
assert m._sfp_list[2] is None
assert m.sfp_initialized_count == 2
# Get all SFPs, but there are SFP already created, only None SFP created
sfp_list = m.get_all_sfps()
assert len(sfp_list) == 3
assert m.sfp_initialized_count == 3
assert filter(lambda x: x is not None, sfp_list)
assert id(sfp1) == id(sfp_list[0])
assert id(sfp2) == id(sfp_list[1])
# Get all SFPs, no SFP yet, all SFP created
m._sfp_list = []
m.sfp_initialized_count = 0
sfp_list = m.get_all_sfps()
assert len(sfp_list) == 3
assert m.sfp_initialized_count == 3
def test_check_state(self):
utils.read_int_from_file = mock.MagicMock(return_value=0)
m = Module(1)
m._sfp_list.append(1)
m._thermal_list.append(1)
m._get_seq_no = mock.MagicMock(return_value=0)
# both seq number and state no change, do not re-init module
m._check_state()
assert len(m._sfp_list) > 0
assert len(m._thermal_list) > 0
# seq number changes, but state keeps deactivated, no need re-init module
m._get_seq_no = mock.MagicMock(return_value=1)
m._check_state()
assert len(m._sfp_list) > 0
assert len(m._thermal_list) > 0
# seq number not change, state changes from deactivated to activated, need re-init module
utils.read_int_from_file = mock.MagicMock(return_value=1)
m._check_state()
assert len(m._sfp_list) == 0
assert len(m._thermal_list) == 0
# seq number changes, state keeps activated, which means the module has been replaced, need re-init module
m._sfp_list.append(1)
m._thermal_list.append(1)
m._get_seq_no = mock.MagicMock(return_value=2)
m._check_state()
assert len(m._sfp_list) == 0
assert len(m._thermal_list) == 0
# seq number not change, state changes from activated to deactivated, need re-init module
m._sfp_list.append(1)
m._thermal_list.append(1)
utils.read_int_from_file = mock.MagicMock(return_value=0)
m._check_state()
assert len(m._sfp_list) == 0
assert len(m._thermal_list) == 0
def test_module_vpd(self):
m = Module(1)
m.vpd_parser.vpd_file = os.path.join(test_path, 'mock_psu_vpd')
assert m.get_model() == 'MTEF-PSF-AC-C'
assert m.get_serial() == 'MT1946X07684'
assert m.get_revision() == 'A3'
m.vpd_parser.vpd_file = 'not exists'
assert m.get_model() == 'N/A'
assert m.get_serial() == 'N/A'
assert m.get_revision() == 'N/A'
m.vpd_parser.vpd_file_last_mtime = None
m.vpd_parser.vpd_file = os.path.join(test_path, 'mock_psu_vpd')
assert m.get_model() == 'MTEF-PSF-AC-C'
assert m.get_serial() == 'MT1946X07684'
assert m.get_revision() == 'A3'

View File

@ -0,0 +1,112 @@
#
# 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 os
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 import utils
from sonic_platform.psu import FixedPsu, Psu
class TestPsu:
def test_fixed_psu(self):
psu = FixedPsu(0)
assert psu.get_name() == 'PSU 1'
assert psu.get_model() == 'N/A'
assert psu.get_serial() == 'N/A'
assert psu.get_revision() == 'N/A'
utils.read_int_from_file = mock.MagicMock(return_value=1)
assert psu.get_powergood_status()
utils.read_int_from_file = mock.MagicMock(return_value=0)
assert not psu.get_powergood_status()
assert psu.get_presence()
assert psu.get_voltage() is None
assert psu.get_current() is None
assert psu.get_power() is None
assert psu.get_position_in_parent() == 1
assert psu.is_replaceable() is False
assert psu.get_temperature() is None
assert psu.get_temperature_high_threshold() is None
def test_psu(self):
psu = Psu(0)
assert len(psu._fan_list) == 1
assert psu.get_fan(0).get_name() == 'psu1_fan1'
mock_sysfs_content = {
psu.psu_presence: 1,
psu.psu_oper_status: 1,
psu.psu_voltage: 10234,
psu.psu_current: 20345,
psu.psu_power: 30456,
psu.psu_temp: 40567,
psu.psu_temp_threshold: 50678
}
def mock_read_int_from_file(file_path, **kwargs):
return mock_sysfs_content[file_path]
utils.read_int_from_file = mock_read_int_from_file
assert psu.get_presence() is True
mock_sysfs_content[psu.psu_presence] = 0
assert psu.get_presence() is False
assert psu.get_powergood_status() is True
mock_sysfs_content[psu.psu_oper_status] = 0
assert psu.get_powergood_status() is False
assert psu.get_voltage() is None
assert psu.get_current() is None
assert psu.get_power() is None
assert psu.get_temperature() is None
assert psu.get_temperature_high_threshold() is None
mock_sysfs_content[psu.psu_oper_status] = 1
assert psu.get_voltage() == 10.234
assert psu.get_current() == 20.345
assert psu.get_power() == 0.030456
assert psu.get_temperature() == 40.567
assert psu.get_temperature_high_threshold() == 50.678
assert psu.get_position_in_parent() == 1
assert psu.is_replaceable() is True
def test_psu_vpd(self):
psu = Psu(0)
psu.vpd_parser.vpd_file = os.path.join(test_path, 'mock_psu_vpd')
assert psu.get_model() == 'MTEF-PSF-AC-C'
assert psu.get_serial() == 'MT1946X07684'
assert psu.get_revision() == 'A3'
psu.vpd_parser.vpd_file = 'not exists'
assert psu.get_model() == 'N/A'
assert psu.get_serial() == 'N/A'
assert psu.get_revision() == 'N/A'
psu.vpd_parser.vpd_file_last_mtime = None
psu.vpd_parser.vpd_file = os.path.join(test_path, 'mock_psu_vpd')
assert psu.get_model() == 'MTEF-PSF-AC-C'
assert psu.get_serial() == 'MT1946X07684'
assert psu.get_revision() == 'A3'

View File

@ -16,115 +16,56 @@
#
import os
import sys
import pytest
from mock import MagicMock
from .mock_platform import MockFan
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)
os.environ["PLATFORM_API_UNIT_TESTING"] = "1"
from sonic_py_common import device_info
from sonic_platform.sfp import SFP, SX_PORT_MODULE_STATUS_INITIALIZING, SX_PORT_MODULE_STATUS_PLUGGED, SX_PORT_MODULE_STATUS_UNPLUGGED, SX_PORT_MODULE_STATUS_PLUGGED_WITH_ERROR, SX_PORT_MODULE_STATUS_PLUGGED_DISABLED
from sonic_platform.chassis import Chassis
class TestSfp:
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_linecard_count', mock.MagicMock(return_value=8))
@mock.patch('sonic_platform.device_data.DeviceDataManager.get_linecard_max_port_count')
def test_sfp_index(self, mock_max_port):
sfp = SFP(0)
assert sfp.sdk_index == 0
assert sfp.index == 1
def mock_get_platform():
return 'x86_64-mlnx_msn2410-r0'
mock_max_port.return_value = 16
sfp = SFP(sfp_index=0, slot_id=1, linecard_port_count=16, lc_name='LINE-CARD1')
assert sfp.sdk_index == 0
assert sfp.index == 1
sfp = SFP(sfp_index=5, slot_id=3, linecard_port_count=16, lc_name='LINE-CARD1')
assert sfp.sdk_index == 5
assert sfp.index == 38
def mock_read_eeprom_specific_bytes(self, offset, num_bytes):
return None
sfp = SFP(sfp_index=1, slot_id=1, linecard_port_count=4, lc_name='LINE-CARD1')
assert sfp.sdk_index == 1
assert sfp.index == 5
def mock_get_sdk_handle(self):
if not self.sdk_handle:
self.sdk_handle = 1
return self.sdk_handle
def mock_get_sfp_error_code(self):
return self.oper_code, self.error_code
device_info.get_platform = mock_get_platform
SFP._read_eeprom_specific_bytes = mock_read_eeprom_specific_bytes
SFP._get_error_code = mock_get_sfp_error_code
Chassis.get_sdk_handle = mock_get_sdk_handle
def test_sfp_partial_and_then_full_initialize():
"""
Verify SFP initialization flow (partial and then full):
1. get_sfp to tirgger a partial initialization
2. get_sfp for another SPF module and verify the partial initialization isn't executed again
3. get_all_sfps to trigger a full initialization
"""
chassis = Chassis()
# Fetch a sfp
# This should trigger SFP modules be partial initialized
sfp1 = chassis.get_sfp(1)
# Verify the SFP list has been created
assert len(chassis._sfp_list) == chassis.PORT_END + 1
assert chassis.sfp_module_partial_initialized == True
assert chassis.sfp_module_full_initialized == False
# Fetch another SFP module
sfp2 = chassis.get_sfp(2)
# Verify the previous SFP module isn't changed
assert sfp1 == chassis.get_sfp(1)
# Fetch all SFP modules
allsfp = chassis.get_all_sfps()
# Verify sfp1 and sfp2 aren't changed
assert sfp1 == chassis.get_sfp(1)
assert sfp2 == chassis.get_sfp(2)
# Verify the SFP has been fully initialized
assert chassis.sfp_module_partial_initialized == True
assert chassis.sfp_module_full_initialized == True
def test_sfp_full_initialize_without_partial():
"""
Verify SFP initialization flow (full):
1. get_all_sfps to trigger a full initialization
2. get_sfp for a certain SFP module and verify the partial initialization isn't executed again
"""
chassis = Chassis()
# Fetch all SFP modules
allsfp = chassis.get_all_sfps()
# Verify the SFP has been fully initialized
assert chassis.sfp_module_partial_initialized == True
assert chassis.sfp_module_full_initialized == True
for sfp in allsfp:
assert sfp is not None
# Verify when get_sfp is called, the SFP modules won't be initialized again
sfp1 = allsfp[0]
assert sfp1 == chassis.get_sfp(1)
def test_sfp_get_error_status():
@mock.patch('sonic_platform.sfp.SFP._read_eeprom_specific_bytes', mock.MagicMock(return_value=None))
@mock.patch('sonic_platform.sfp.SFP._get_error_code')
@mock.patch('sonic_platform.chassis.Chassis.get_num_sfps', mock.MagicMock(return_value=2))
def test_sfp_get_error_status(self, mock_get_error_code):
chassis = Chassis()
# Fetch an SFP module to test
sfp = chassis.get_sfp(1)
description_dict = sfp._get_error_description_dict()
sfp.oper_code = SX_PORT_MODULE_STATUS_PLUGGED_WITH_ERROR
for error in description_dict.keys():
sfp.error_code = error
mock_get_error_code.return_value = (SX_PORT_MODULE_STATUS_PLUGGED_WITH_ERROR, error)
description = sfp.get_error_description()
assert description == description_dict[sfp.error_code]
assert description == description_dict[error]
sfp.error_code = -1
mock_get_error_code.return_value = (SX_PORT_MODULE_STATUS_PLUGGED_WITH_ERROR, -1)
description = sfp.get_error_description()
assert description == "Unknown error (-1)"
@ -135,7 +76,7 @@ def test_sfp_get_error_status():
(SX_PORT_MODULE_STATUS_PLUGGED_DISABLED, "Disabled")
]
for oper_code, expected_description in expected_description_list:
sfp.oper_code = oper_code
mock_get_error_code.return_value = (oper_code, -1)
description = sfp.get_error_description()
assert description == expected_description

View File

@ -15,10 +15,9 @@
# limitations under the License.
#
import os
import select
import sys
from mock import MagicMock
from mock import MagicMock, patch
test_path = os.path.dirname(os.path.abspath(__file__))
modules_path = os.path.dirname(test_path)
@ -30,8 +29,8 @@ class TestSfpEvent(object):
@classmethod
def setup_class(cls):
os.environ["MLNX_PLATFORM_API_UNIT_TESTING"] = "1"
select.select = MagicMock(return_value=([99], None, None))
@patch('select.select', MagicMock(return_value=([99], None, None)))
def test_check_sfp_status(self):
from sonic_platform.sfp_event import SDK_SFP_STATE_IN, SDK_SFP_STATE_OUT, SDK_SFP_STATE_ERR
from sonic_platform.sfp_event import SDK_ERRORS_TO_ERROR_BITS, SDK_ERRORS_TO_DESCRIPTION, SDK_SFP_BLOCKING_ERRORS

View File

@ -0,0 +1,239 @@
#
# 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 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:
def test_chassis_thermal(self):
from sonic_platform.thermal import THERMAL_NAMING_RULE
os.path.exists = mock.MagicMock(return_value=True)
DeviceDataManager.get_gearbox_count = mock.MagicMock(return_value=2)
DeviceDataManager.get_cpu_thermal_count = mock.MagicMock(return_value=2)
DeviceDataManager.get_platform_name = mock.MagicMock(return_value='x86_64-mlnx_msn2700-r0')
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
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_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)
def test_check_thermal_zone_temperature(self):
from sonic_platform.thermal import Thermal, THERMAL_ZONE_FOLDER_WILDCARD, THERMAL_ZONE_THRESHOLD_FILE, THERMAL_ZONE_TEMP_FILE
from sonic_platform import utils
glob.iglob = mock.MagicMock(return_value=['thermal_zone1', 'thermal_zone2'])
utils.read_int_from_file = mock.MagicMock(side_effect=Exception(''))
assert not Thermal.check_thermal_zone_temperature()
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('thermal_zone1', THERMAL_ZONE_THRESHOLD_FILE)] = 25
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 30
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_THRESHOLD_FILE)] = 25
mock_file_content[os.path.join('thermal_zone2', THERMAL_ZONE_TEMP_FILE)] = 24
assert not Thermal.check_thermal_zone_temperature()
mock_file_content[os.path.join('thermal_zone1', THERMAL_ZONE_TEMP_FILE)] = 24
assert Thermal.check_thermal_zone_temperature()
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

View File

@ -29,9 +29,20 @@ from sonic_platform.thermal_manager import ThermalManager
from sonic_platform.thermal_infos import FanInfo, PsuInfo
from sonic_platform.fan import Fan
from sonic_platform.thermal import Thermal
from sonic_platform.device_data import DeviceDataManager
Thermal.check_thermal_zone_temperature = MagicMock()
Thermal.set_thermal_algorithm_status = MagicMock()
@pytest.fixture(scope='module', autouse=True)
def configure_mocks():
check_thermal_zone_temperature = Thermal.check_thermal_zone_temperature
set_thermal_algorithm_status = Thermal.set_thermal_algorithm_status
Thermal.check_thermal_zone_temperature = MagicMock()
Thermal.set_thermal_algorithm_status = MagicMock()
yield
Thermal.check_thermal_zone_temperature = check_thermal_zone_temperature
Thermal.set_thermal_algorithm_status = set_thermal_algorithm_status
@pytest.fixture(scope='session', autouse=True)
@ -489,7 +500,7 @@ def check_minimum_table_data(platform, minimum_table):
def test_dynamic_minimum_policy(thermal_manager):
from sonic_platform.thermal_conditions import MinCoolingLevelChangeCondition
from sonic_platform.thermal_actions import ChangeMinCoolingLevelAction
from sonic_platform.thermal_infos import ChassisInfo
from sonic_platform.thermal_infos import ChassisInfo, FanInfo
from sonic_platform.thermal import Thermal
from sonic_platform.fan import Fan
ThermalManager.initialize()
@ -516,10 +527,15 @@ def test_dynamic_minimum_policy(thermal_manager):
assert MinCoolingLevelChangeCondition.temperature == 25
chassis = MockChassis()
chassis.platform_name = 'invalid'
info = ChassisInfo()
info._chassis = chassis
thermal_info_dict = {ChassisInfo.INFO_NAME: info}
fan_info = FanInfo()
thermal_info_dict = {
ChassisInfo.INFO_NAME: info,
FanInfo.INFO_NAME: fan_info
}
DeviceDataManager.get_platform_name = MagicMock(return_value=None)
Fan.get_cooling_level = MagicMock(return_value=5)
Fan.set_cooling_level = MagicMock()
action.execute(thermal_info_dict)
@ -527,7 +543,8 @@ def test_dynamic_minimum_policy(thermal_manager):
Fan.set_cooling_level.assert_called_with(6, 6)
Fan.set_cooling_level.call_count = 0
chassis.platform_name = 'x86_64-mlnx_msn2700-r0'
DeviceDataManager.get_platform_name = MagicMock(return_value='x86_64-mlnx_msn2700-r0')
print('Before execute')
action.execute(thermal_info_dict)
assert Fan.min_cooling_level == 3
Fan.set_cooling_level.assert_called_with(3, 5)

View File

@ -0,0 +1,118 @@
#
# 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 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 import utils
class TestUtils:
def test_read_file(self):
ret = utils.read_str_from_file('not exist', 'default return')
assert ret == 'default return'
with pytest.raises(IOError):
ret = utils.read_str_from_file('not exist', 'default return', raise_exception=True)
assert ret == 'default return'
ret = utils.read_int_from_file('not exist', 100)
assert ret == 100
with pytest.raises(IOError):
ret = utils.read_int_from_file('not exist', 200, raise_exception=True)
assert ret == 200
ret = utils.read_float_from_file('not exist', 3.14)
assert ret == 3.14
with pytest.raises(IOError):
ret = utils.read_float_from_file('not exist', 2.25, raise_exception=True)
assert ret == 2.25
def test_write_file(self):
file_path = '/tmp/test.txt'
utils.write_file(file_path, ' hello ')
assert utils.read_str_from_file(file_path) == 'hello'
utils.write_file(file_path, '123 ')
assert utils.read_int_from_file(file_path) == 123
utils.write_file(file_path, '3.14 ')
assert utils.read_float_from_file(file_path) == 3.14
with pytest.raises(IOError):
utils.write_file('/not/exist/file', '123', raise_exception=True)
def test_pre_initialize(self):
mock_call = mock.MagicMock()
class A:
@utils.pre_initialize(mock_call)
def func(self):
pass
A().func()
assert mock_call.call_count == 1
def test_pre_initialize_one(self):
mock_call = mock.MagicMock()
class A:
@utils.pre_initialize_one(mock_call)
def func(self, index):
pass
a = A()
a.func(34)
mock_call.assert_called_once_with(a, 34)
def test_read_only_cache(self):
value = 100
def func():
return value
assert func() == 100
value = 1000
assert func() == 1000
@utils.read_only_cache()
def func():
return value
assert func() == 1000
value = 10000
assert func() == 1000
@mock.patch('sonic_py_common.logger.Logger.log_debug')
def test_default_return(self, mock_log):
@utils.default_return(100, log_func=mock_log)
def func():
raise RuntimeError('')
assert func() == 100
assert mock_log.call_count == 1