From 5e6c20481d3396acd87565a3e9f0b918eaeade1f Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Thu, 14 May 2020 01:01:32 +0800 Subject: [PATCH] [Mellanox] Enhancement for fan led management (#4437) --- .../thermal_policy.json | 1 + .../thermal_policy.json | 1 + .../thermal_policy.json | 1 + .../sonic_platform/chassis.py | 47 ++-- .../sonic_platform/device_data.py | 159 ++++++++++++++ .../mlnx-platform-api/sonic_platform/fan.py | 138 ++---------- .../sonic_platform/fan_drawer.py | 104 +++++++++ .../mlnx-platform-api/sonic_platform/led.py | 201 ++++++++++++++++++ .../mlnx-platform-api/sonic_platform/psu.py | 135 +++--------- .../sonic_platform/thermal.py | 4 +- .../mlnx-platform-api/tests/mock_platform.py | 4 + .../mlnx-platform-api/tests/test_fan_api.py | 46 +++- src/sonic-platform-common | 2 +- 13 files changed, 593 insertions(+), 250 deletions(-) create mode 120000 device/mellanox/x86_64-mlnx_msn3420-r0/thermal_policy.json create mode 120000 device/mellanox/x86_64-mlnx_msn4600c-r0/thermal_policy.json create mode 120000 device/mellanox/x86_64-mlnx_msn4700-r0/thermal_policy.json create mode 100644 platform/mellanox/mlnx-platform-api/sonic_platform/fan_drawer.py create mode 100644 platform/mellanox/mlnx-platform-api/sonic_platform/led.py diff --git a/device/mellanox/x86_64-mlnx_msn3420-r0/thermal_policy.json b/device/mellanox/x86_64-mlnx_msn3420-r0/thermal_policy.json new file mode 120000 index 0000000000..5a25cd87f7 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn3420-r0/thermal_policy.json @@ -0,0 +1 @@ +../x86_64-mlnx_msn2700-r0/thermal_policy.json \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn4600c-r0/thermal_policy.json b/device/mellanox/x86_64-mlnx_msn4600c-r0/thermal_policy.json new file mode 120000 index 0000000000..5a25cd87f7 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn4600c-r0/thermal_policy.json @@ -0,0 +1 @@ +../x86_64-mlnx_msn2700-r0/thermal_policy.json \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn4700-r0/thermal_policy.json b/device/mellanox/x86_64-mlnx_msn4700-r0/thermal_policy.json new file mode 120000 index 0000000000..5a25cd87f7 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn4700-r0/thermal_policy.json @@ -0,0 +1 @@ +../x86_64-mlnx_msn2700-r0/thermal_policy.json \ No newline at end of file diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py index 5ecf3c150d..2e9ad7210a 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py @@ -12,10 +12,10 @@ try: from sonic_platform_base.chassis_base import ChassisBase from sonic_platform_base.component_base import ComponentBase from sonic_device_util import get_machine_info + from sonic_device_util import get_platform_info from sonic_daemon_base.daemon_base import Logger from os import listdir from os.path import isfile, join - from glob import glob import sys import io import re @@ -61,12 +61,14 @@ class Chassis(ChassisBase): # Initialize SKU name self.sku_name = self._get_sku_name() - self.platform_name = self._get_platform_name() + mi = get_machine_info() if mi is not None: self.name = mi['onie_platform'] + self.platform_name = get_platform_info(mi) else: self.name = self.sku_name + self.platform_name = self._get_platform_name() # move the initialization of each components to their dedicated initializer # which will be called from platform @@ -86,36 +88,29 @@ class Chassis(ChassisBase): # Initialize PSU list self.psu_module = Psu for index in range(MLNX_NUM_PSU): - psu = Psu(index, self.sku_name) + psu = Psu(index, self.platform_name) self._psu_list.append(psu) def initialize_fan(self): + from .device_data import DEVICE_DATA from sonic_platform.fan import Fan - from sonic_platform.fan import FAN_PATH - self.fan_module = Fan - self.fan_path = FAN_PATH - # Initialize FAN list - multi_rotor_in_drawer = False - num_of_fan, num_of_drawer = self._extract_num_of_fans_and_fan_drawers() - multi_rotor_in_drawer = num_of_fan > num_of_drawer + from .fan_drawer import RealDrawer, VirtualDrawer - # Fan's direction isn't supported on spectrum 1 devices for now - mst_dev_list = glob(MST_DEVICE_NAME_PATTERN) - if not mst_dev_list: - raise RuntimeError("Can't get chip type due to {} not found".format(MST_DEVICE_NAME_PATTERN)) - m = re.search(MST_DEVICE_RE_PATTERN, mst_dev_list[0]) - if m.group(1) == SPECTRUM1_CHIP_ID: - has_fan_dir = False - else: - has_fan_dir = True - - for index in range(num_of_fan): - if multi_rotor_in_drawer: - fan = Fan(has_fan_dir, index, index/2, False, self.platform_name) - else: - fan = Fan(has_fan_dir, index, index, False, self.platform_name) - self._fan_list.append(fan) + fan_data = DEVICE_DATA[self.platform_name]['fans'] + drawer_num = fan_data['drawer_num'] + drawer_type = fan_data['drawer_type'] + fan_num_per_drawer = fan_data['fan_num_per_drawer'] + drawer_ctor = RealDrawer if drawer_type == 'real' else VirtualDrawer + fan_index = 0 + for drawer_index in range(drawer_num): + drawer = drawer_ctor(drawer_index, fan_data) + self._fan_drawer_list.append(drawer) + for index in range(fan_num_per_drawer): + fan = Fan(fan_index, drawer) + fan_index += 1 + drawer._fan_list.append(fan) + self._fan_list.append(fan) def initialize_sfp(self): diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py b/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py index 35b1f14d5b..fd3c7ad50e 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py @@ -5,6 +5,19 @@ DEVICE_DATA = { "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} } + }, + 'fans': { + 'drawer_num': 4, + 'drawer_type': 'real', + 'fan_num_per_drawer': 2, + 'support_fan_direction': False, + 'hot_swappable': True + }, + 'psus': { + 'psu_num': 2, + 'fan_num_per_psu': 1, + 'hot_swappable': True, + 'led_num': 1 } }, 'x86_64-mlnx_msn2740-r0': { @@ -13,6 +26,19 @@ DEVICE_DATA = { "unk_trust": {"-127:120":13}, "unk_untrust": {"-127:15":13, "16:25":14 , "26:30":15, "31:120":17}, } + }, + 'fans': { + 'drawer_num': 4, + 'drawer_type': 'real', + 'fan_num_per_drawer': 1, + 'support_fan_direction': False, + 'hot_swappable': True + }, + 'psus': { + 'psu_num': 2, + 'fan_num_per_psu': 1, + 'hot_swappable': True, + 'led_num': 1 } }, 'x86_64-mlnx_msn2100-r0': { @@ -21,6 +47,19 @@ DEVICE_DATA = { "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} } + }, + 'fans': { + 'drawer_num': 1, + 'drawer_type': 'virtual', + 'fan_num_per_drawer': 4, + 'support_fan_direction': False, + 'hot_swappable': False + }, + 'psus': { + 'psu_num': 2, + 'fan_num_per_psu': 1, + 'hot_swappable': False, + 'led_num': 2 } }, 'x86_64-mlnx_msn2410-r0': { @@ -29,6 +68,19 @@ DEVICE_DATA = { "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} } + }, + 'fans': { + 'drawer_num': 4, + 'drawer_type': 'real', + 'fan_num_per_drawer': 2, + 'support_fan_direction': False, + 'hot_swappable': True + }, + 'psus': { + 'psu_num': 2, + 'fan_num_per_psu': 1, + 'hot_swappable': True, + 'led_num': 1 } }, 'x86_64-mlnx_msn2010-r0': { @@ -37,6 +89,19 @@ DEVICE_DATA = { "unk_trust": {"-127:120":12}, "unk_untrust": {"-127:15":12, "16:20":13 , "21:30":14, "31:35":15, "36:120":16} } + }, + 'fans': { + 'drawer_num': 1, + 'drawer_type': 'virtual', + 'fan_num_per_drawer': 4, + 'support_fan_direction': False, + 'hot_swappable': False + }, + 'psus': { + 'psu_num': 2, + 'fan_num_per_psu': 1, + 'hot_swappable': False, + 'led_num': 2 } }, 'x86_64-mlnx_msn3700-r0': { @@ -45,6 +110,19 @@ 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': { @@ -53,6 +131,19 @@ 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': { @@ -61,14 +152,82 @@ 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': { + 'thermal': { + 'minimum_table': { + "unk_trust": {"-127:120":16}, + "unk_untrust": {"-127: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': { 'thermal': { 'minimum_table': { "unk_trust": {"-127:120":16}, "unk_untrust": {"-127: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': { + 'thermal': { + 'minimum_table': { + "unk_trust": {"-127:120":16}, + "unk_untrust": {"-127:120":16}, + } + }, + '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 } } } \ No newline at end of file diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py b/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py index adca48befb..d123b6c6c3 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py @@ -13,25 +13,18 @@ import subprocess try: from sonic_platform_base.fan_base import FanBase + from .led import FanLed, ComponentFaultyIndicator except ImportError as e: raise ImportError (str(e) + "- required module not found") -LED_ON = '1' -LED_OFF = '0' - PWM_MAX = 255 FAN_PATH = "/var/run/hw-management/thermal/" -LED_PATH = "/var/run/hw-management/led/" CONFIG_PATH = "/var/run/hw-management/config" # fan_dir isn't supported on Spectrum 1. It is supported on Spectrum 2 and later switches FAN_DIR = "/var/run/hw-management/system/fan_dir" COOLING_STATE_PATH = "/var/run/hw-management/thermal/cooling_cur_state" -# Platforms with unplugable FANs: -# 1. don't have fanX_status and should be treated as always present -platform_with_unplugable_fan = ['x86_64-mlnx_msn2010-r0', 'x86_64-mlnx_msn2100-r0'] - class Fan(FanBase): """Platform-specific Fan class""" @@ -44,21 +37,28 @@ class Fan(FanBase): PSU_FAN_SPEED = ['0x3c', '0x3c', '0x3c', '0x3c', '0x3c', '0x3c', '0x3c', '0x46', '0x50', '0x5a', '0x64'] - def __init__(self, has_fan_dir, fan_index, drawer_index = 1, psu_fan = False, platform = None): + def __init__(self, fan_index, fan_drawer, psu_fan = False): + super(Fan, self).__init__() + # API index is starting from 0, Mellanox platform index is starting from 1 self.index = fan_index + 1 - self.drawer_index = drawer_index + 1 + self.fan_drawer = fan_drawer self.is_psu_fan = psu_fan - self.always_presence = False if platform not in platform_with_unplugable_fan else True + 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) self.fan_min_speed_path = "fan{}_min".format(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_presence_path = "fan{}_status".format(self.drawer_index) self.fan_max_speed_path = "fan{}_max".format(self.index) - self._name = "fan{}".format(fan_index + 1) + 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) @@ -69,15 +69,7 @@ class Fan(FanBase): self.psu_i2c_command_path = os.path.join(CONFIG_PATH, 'fan_command') self.fan_status_path = "fan{}_fault".format(self.index) - self.fan_green_led_path = "led_fan{}_green".format(self.drawer_index) - self.fan_red_led_path = "led_fan{}_red".format(self.drawer_index) - self.fan_orange_led_path = "led_fan{}_orange".format(self.drawer_index) self.fan_pwm_path = "pwm1" - self.fan_led_cap_path = "led_fan{}_capability".format(self.drawer_index) - if has_fan_dir: - self.fan_dir = FAN_DIR - else: - self.fan_dir = None def get_direction(self): @@ -99,20 +91,10 @@ class Fan(FanBase): 1 stands for forward, in other words intake 0 stands for reverse, in other words exhaust """ - if not self.fan_dir or self.is_psu_fan or not self.get_presence(): + if self.is_psu_fan: return self.FAN_DIRECTION_NOT_APPLICABLE - - try: - with open(os.path.join(self.fan_dir), 'r') as fan_dir: - fan_dir_bits = int(fan_dir.read().strip()) - fan_mask = 1 << self.drawer_index - 1 - if fan_dir_bits & fan_mask: - return self.FAN_DIRECTION_INTAKE - else: - return self.FAN_DIRECTION_EXHAUST - except (ValueError, IOError) as e: - raise RuntimeError("Failed to read fan direction status to {}".format(repr(e))) - + else: + return self.fan_drawer.get_direction() def get_name(self): return self._name @@ -150,17 +132,9 @@ class Fan(FanBase): status = 1 else: status = 0 + return status == 1 else: - if self.always_presence: - status = 1 - else: - try: - with open(os.path.join(FAN_PATH, self.fan_presence_path), 'r') as presence_status: - status = int(presence_status.read().strip()) - except (ValueError, IOError): - status = 0 - - return status == 1 + return self.fan_drawer.get_presence() def _get_min_speed_in_rpm(self): @@ -281,18 +255,6 @@ class Fan(FanBase): return status - def _get_led_capability(self): - cap_list = None - try: - with open(os.path.join(LED_PATH, self.fan_led_cap_path), 'r') as fan_led_cap: - caps = fan_led_cap.read() - cap_list = caps.split() - except (ValueError, IOError): - status = 0 - - return cap_list - - def set_status_led(self, color): """ Set led to expected color @@ -304,48 +266,7 @@ class Fan(FanBase): Returns: bool: True if set success, False if fail. """ - led_cap_list = self._get_led_capability() - if led_cap_list is None: - return False - - if self.is_psu_fan: - # PSU fan led status is not able to set - return False - status = False - try: - if color == self.STATUS_LED_COLOR_GREEN: - with open(os.path.join(LED_PATH, self.fan_green_led_path), 'w') as fan_led: - fan_led.write(LED_ON) - status = True - elif color == self.STATUS_LED_COLOR_RED: - # Some fan don't support red led but support orange led, in this case we set led to orange - if self.STATUS_LED_COLOR_RED in led_cap_list: - led_path = os.path.join(LED_PATH, self.fan_red_led_path) - elif self.STATUS_LED_COLOR_ORANGE in led_cap_list: - led_path = os.path.join(LED_PATH, self.fan_orange_led_path) - else: - return False - with open(led_path, 'w') as fan_led: - fan_led.write(LED_ON) - status = True - elif color == self.STATUS_LED_COLOR_OFF: - if self.STATUS_LED_COLOR_GREEN in led_cap_list: - with open(os.path.join(LED_PATH, self.fan_green_led_path), 'w') as fan_led: - fan_led.write(str(LED_OFF)) - if self.STATUS_LED_COLOR_RED in led_cap_list: - with open(os.path.join(LED_PATH, self.fan_red_led_path), 'w') as fan_led: - fan_led.write(str(LED_OFF)) - if self.STATUS_LED_COLOR_ORANGE in led_cap_list: - with open(os.path.join(LED_PATH, self.fan_orange_led_path), 'w') as fan_led: - fan_led.write(str(LED_OFF)) - - status = True - else: - status = False - except (ValueError, IOError): - status = False - - return status + return self.led.set_status(color) def get_status_led(self): @@ -355,26 +276,7 @@ class Fan(FanBase): Returns: A string, one of the predefined STATUS_LED_COLOR_* strings above """ - led_cap_list = self._get_led_capability() - if led_cap_list is None: - return self.STATUS_LED_COLOR_OFF - - try: - with open(os.path.join(LED_PATH, self.fan_green_led_path), 'r') as fan_led: - if LED_OFF != fan_led.read().rstrip('\n'): - return self.STATUS_LED_COLOR_GREEN - if self.STATUS_LED_COLOR_RED in led_cap_list: - with open(os.path.join(LED_PATH, self.fan_red_led_path), 'r') as fan_led: - if LED_OFF != fan_led.read().rstrip('\n'): - return self.STATUS_LED_COLOR_RED - if self.STATUS_LED_COLOR_ORANGE in led_cap_list: - with open(os.path.join(LED_PATH, self.fan_orange_led_path), 'r') as fan_led: - if LED_OFF != fan_led.read().rstrip('\n'): - return self.STATUS_LED_COLOR_RED - except (ValueError, IOError) as e: - raise RuntimeError("Failed to read led status for fan {} due to {}".format(self.index, repr(e))) - - return self.STATUS_LED_COLOR_OFF + return self.led.get_status() def get_speed_tolerance(self): diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/fan_drawer.py b/platform/mellanox/mlnx-platform-api/sonic_platform/fan_drawer.py new file mode 100644 index 0000000000..bed6c7ab04 --- /dev/null +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/fan_drawer.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +############################################################################# +# Mellanox +# +# Module contains an implementation of SONiC Platform Base API and +# provides the Fan Drawer status which are available in the platform +# +############################################################################# + +import os + +try: + from sonic_platform_base.fan_drawer_base import FanDrawerBase + from sonic_platform_base.fan_base import FanBase + from .led import FanLed, SharedLed +except ImportError as e: + raise ImportError (str(e) + "- required module not found") + + +class MellanoxFanDrawer(FanDrawerBase): + def __init__(self, index, fan_data): + 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 + + def get_index(self): + return self._index + + def get_led(self): + 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 + + def get_direction(self): + if not self._fan_data['support_fan_direction'] or not self.get_presence(): + return FanBase.FAN_DIRECTION_NOT_APPLICABLE + + try: + from .fan import FAN_DIR + with open(FAN_DIR, 'r') as fan_dir: + fan_dir_bits = int(fan_dir.read()) + fan_mask = 1 << self._index - 1 + if fan_dir_bits & fan_mask: + return FanBase.FAN_DIRECTION_INTAKE + else: + return FanBase.FAN_DIRECTION_EXHAUST + except (ValueError, IOError) as e: + raise RuntimeError("Failed to read fan direction status to {}".format(repr(e))) + + def set_status_led(self, color): + """ + Sets the state of the fan drawer status LED + + Args: + color: A string representing the color with which to set the + fan drawer status LED + + Returns: + bool: True if status LED state is set successfully, False if not + """ + return True + + def get_status_led(self): + """ + Gets the state of the fan drawer LED + + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings above + """ + return self._led.get_status() + + +class RealDrawer(MellanoxFanDrawer): + def __init__(self, index, fan_data): + super(RealDrawer, self).__init__(index, fan_data) + self._name = 'drawer{}'.format(self._index) + self._led = SharedLed(FanLed(self._index)) + + def get_name(self): + return self._name + + +class VirtualDrawer(MellanoxFanDrawer): + def __init__(self, index, fan_data): + super(VirtualDrawer, self).__init__(index, fan_data) + self._led = SharedLed(FanLed(None)) + + def get_name(self): + return 'N/A' diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/led.py b/platform/mellanox/mlnx-platform-api/sonic_platform/led.py new file mode 100644 index 0000000000..ebc2de25a5 --- /dev/null +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/led.py @@ -0,0 +1,201 @@ +import os + + +class Led(object): + STATUS_LED_COLOR_GREEN = 'green' + STATUS_LED_COLOR_RED = 'red' + STATUS_LED_COLOR_ORANGE = 'orange' + STATUS_LED_COLOR_OFF = 'off' + + LED_ON = '1' + LED_OFF = '0' + + LED_PATH = "/var/run/hw-management/led/" + + def set_status(self, color): + led_cap_list = self.get_capability() + if led_cap_list is None: + return False + + status = False + try: + if color == Led.STATUS_LED_COLOR_GREEN: + with open(self.get_green_led_path(), 'w') as led: + led.write(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 + if Led.STATUS_LED_COLOR_RED in led_cap_list: + led_path = self.get_red_led_path() + elif Led.STATUS_LED_COLOR_ORANGE in led_cap_list: + led_path = self.get_orange_led_path() + else: + return False + + with open(led_path, 'w') as led: + led.write(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) + 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) + 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) + + status = True + else: + status = False + except (ValueError, IOError): + status = False + + return status + + def get_status(self): + led_cap_list = self.get_capability() + if led_cap_list is None: + return Led.STATUS_LED_COLOR_OFF + + try: + with open(self.get_green_led_path(), 'r') as led: + if Led.LED_OFF != led.read().rstrip('\n'): + 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'): + 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'): + return Led.STATUS_LED_COLOR_RED + except (ValueError, IOError) as e: + raise RuntimeError("Failed to read led status due to {}".format(repr(e))) + + return Led.STATUS_LED_COLOR_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 = caps.split() + except (ValueError, IOError): + pass + + return cap_list + + def get_green_led_path(self): + pass + + def get_red_led_path(self): + pass + + def get_orange_led_path(self): + pass + + def get_led_cap_path(self): + pass + + +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)) + 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") + + self.set_status(Led.STATUS_LED_COLOR_GREEN) + + 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 + + +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)) + 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") + + self.set_status(Led.STATUS_LED_COLOR_GREEN) + + 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 + + +class SharedLed(object): + LED_PRIORITY = { + Led.STATUS_LED_COLOR_RED: 0, + Led.STATUS_LED_COLOR_GREEN: 1 + } + + def __init__(self, led): + self._led = led + self._virtual_leds = [] + + def add_virtual_leds(self, led): + self._virtual_leds.append(led) + + def update_status_led(self): + target_color = Led.STATUS_LED_COLOR_GREEN + for virtual_led in self._virtual_leds: + if SharedLed.LED_PRIORITY[virtual_led.get_led_color()] < SharedLed.LED_PRIORITY[target_color]: + target_color = virtual_led.get_led_color() + + return self._led.set_status(target_color) + + def get_status(self): + return self._led.get_status() + + +class ComponentFaultyIndicator(object): + def __init__(self, shared_led): + self._color = Led.STATUS_LED_COLOR_GREEN + self._shared_led = shared_led + self._shared_led.add_virtual_leds(self) + + def set_status(self, color): + self._color = color + return self._shared_led.update_status_led() + + def get_led_color(self): + return self._color + + def get_status(self): + return self._shared_led.get_status() diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py b/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py index 1486dc0bc8..a323132faf 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py @@ -13,11 +13,11 @@ try: from sonic_platform_base.psu_base import PsuBase from sonic_daemon_base.daemon_base import Logger from sonic_platform.fan import Fan + from .led import PsuLed, SharedLed, ComponentFaultyIndicator + from .device_data import DEVICE_DATA except ImportError as e: raise ImportError (str(e) + "- required module not found") -LED_ON = '1' -LED_OFF = '0' # Global logger class instance logger = Logger() @@ -28,16 +28,11 @@ PSU_CURRENT = "current" PSU_VOLTAGE = "voltage" PSU_POWER = "power" -LED_PATH = "/var/run/hw-management/led/" - -# SKUs with unplugable PSUs: -# 1. don't have psuX_status and should be treated as always present -# 2. don't have voltage, current and power values -hwsku_dict_with_unplugable_psu = ['ACS-MSN2010', 'ACS-MSN2100'] - -# in most SKUs the file psuX_curr, psuX_volt and psuX_power contain current, voltage and power data respectively. +# 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 -hwsku_dict_psu = {'ACS-MSN3700': 1, 'ACS-MSN3700C': 1, 'ACS-MSN3800': 1, 'Mellanox-SN3800-D112C8': 1, 'ACS-MSN4700': 1, 'ACS-MSN3420': 1, 'ACS-MSN4600C': 1} + +platform_dict_psu = {'x86_64-mlnx_msn3700-r0': 1, 'x86_64-mlnx_msn3700c-r0': 1, 'x86_64-mlnx_msn3800-r0': 1, 'x86_64-mlnx_msn4700-r0': 1} + psu_profile_list = [ # default filename convention { @@ -56,9 +51,9 @@ psu_profile_list = [ class Psu(PsuBase): """Platform-specific Psu class""" - STATUS_LED_COLOR_ORANGE = "orange" + shared_led = None - def __init__(self, psu_index, sku): + def __init__(self, psu_index, platform): global psu_list PsuBase.__init__(self) # PSU is 1-based on Mellanox platform @@ -66,17 +61,19 @@ class Psu(PsuBase): 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 SKUs + #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 sku in hwsku_dict_psu: - filemap = psu_profile_list[hwsku_dict_psu[sku]] + if platform in platform_dict_psu: + filemap = psu_profile_list[platform_dict_psu[platform]] else: filemap = psu_profile_list[0] - if sku in hwsku_dict_with_unplugable_psu: - self.always_presence = True + self.psu_data = DEVICE_DATA[platform]['psus'] + + if not self.psu_data['hot_swappable']: + self.always_present = True self.psu_voltage = None self.psu_current = None self.psu_power = None @@ -84,7 +81,7 @@ class Psu(PsuBase): self.psu_temp = None self.psu_temp_threshold = None else: - self.always_presence = False + 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 @@ -105,14 +102,14 @@ class Psu(PsuBase): self.psu_temp_threshold = os.path.join(self.psu_path, 'thermal/psu{}_temp_max'.format(self.index)) # unplugable PSU has no FAN - if sku not in hwsku_dict_with_unplugable_psu: - fan = Fan(False, psu_index, psu_index, True) + if self.psu_data['hot_swappable']: + fan = Fan(psu_index, None, True) self._fan_list.append(fan) - self.psu_green_led_path = "led_psu_green" - self.psu_red_led_path = "led_psu_red" - self.psu_orange_led_path = "led_psu_orange" - self.psu_led_cap_path = "led_psu_capability" + if self.psu_data['led_num'] == 1: + self.led = ComponentFaultyIndicator(Psu.get_shared_led()) + else: # 2010/2100 + self.led = PsuLed(self.index) def get_name(self): @@ -151,8 +148,8 @@ class Psu(PsuBase): Returns: bool: True if PSU is present, False if not """ - if self.always_presence: - return self.always_presence + if self.always_present: + return self.always_present else: status = self._read_generic_file(self.psu_presence, 0) return status == 1 @@ -199,19 +196,6 @@ class Psu(PsuBase): else: return None - - def _get_led_capability(self): - cap_list = None - try: - with open(os.path.join(LED_PATH, self.psu_led_cap_path), 'r') as psu_led_cap: - caps = psu_led_cap.read() - cap_list = caps.split() - except (ValueError, IOError): - status = 0 - - return cap_list - - def set_status_led(self, color): """ Sets the state of the PSU status LED @@ -226,45 +210,7 @@ class Psu(PsuBase): Notes: Only one led for all PSUs. """ - led_cap_list = self._get_led_capability() - if led_cap_list is None: - return False - - status = False - try: - if color == self.STATUS_LED_COLOR_GREEN: - with open(os.path.join(LED_PATH, self.psu_green_led_path), 'w') as psu_led: - psu_led.write(LED_ON) - status = True - elif color == self.STATUS_LED_COLOR_RED: - # Some fan don't support red led but support orange led, in this case we set led to orange - if self.STATUS_LED_COLOR_RED in led_cap_list: - led_path = os.path.join(LED_PATH, self.psu_red_led_path) - elif self.STATUS_LED_COLOR_ORANGE in led_cap_list: - led_path = os.path.join(LED_PATH, self.psu_orange_led_path) - else: - return False - with open(led_path, 'w') as psu_led: - psu_led.write(LED_ON) - status = True - elif color == self.STATUS_LED_COLOR_OFF: - if self.STATUS_LED_COLOR_GREEN in led_cap_list: - with open(os.path.join(LED_PATH, self.psu_green_led_path), 'w') as psu_led: - psu_led.write(str(LED_OFF)) - if self.STATUS_LED_COLOR_RED in led_cap_list: - with open(os.path.join(LED_PATH, self.psu_red_led_path), 'w') as psu_led: - psu_led.write(str(LED_OFF)) - if self.STATUS_LED_COLOR_ORANGE in led_cap_list: - with open(os.path.join(LED_PATH, self.psu_orange_led_path), 'w') as psu_led: - psu_led.write(str(LED_OFF)) - - status = True - else: - status = False - except (ValueError, IOError): - status = False - - return status + return self.led.set_status(color) def get_status_led(self): @@ -274,26 +220,10 @@ class Psu(PsuBase): Returns: A string, one of the predefined STATUS_LED_COLOR_* strings above """ - led_cap_list = self._get_led_capability() - if led_cap_list is None: - return self.STATUS_LED_COLOR_OFF - - try: - with open(os.path.join(LED_PATH, self.psu_green_led_path), 'r') as psu_led: - if LED_OFF != psu_led.read().rstrip('\n'): - return self.STATUS_LED_COLOR_GREEN - if self.STATUS_LED_COLOR_RED in led_cap_list: - with open(os.path.join(LED_PATH, self.psu_red_led_path), 'r') as psu_led: - if LED_OFF != psu_led.read().rstrip('\n'): - return self.STATUS_LED_COLOR_RED - if self.STATUS_LED_COLOR_ORANGE in led_cap_list: - with open(os.path.join(LED_PATH, self.psu_orange_led_path), 'r') as psu_led: - if LED_OFF != psu_led.read().rstrip('\n'): - return self.STATUS_LED_COLOR_RED - except (ValueError, IOError) as e: - raise RuntimeError("Failed to read led status for psu due to {}".format(repr(e))) - - return self.STATUS_LED_COLOR_OFF + 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): @@ -312,6 +242,12 @@ class Psu(PsuBase): else: return True, "" + @classmethod + def get_shared_led(cls): + if not cls.shared_led: + cls.shared_led = SharedLed(PsuLed(None)) + return cls.shared_led + def get_temperature(self): """ Retrieves current temperature reading from PSU @@ -367,4 +303,3 @@ class Psu(PsuBase): """ # hw-management doesn't expose those sysfs for now raise NotImplementedError - diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/thermal.py b/platform/mellanox/mlnx-platform-api/sonic_platform/thermal.py index be91cf9d52..3c25ebb642 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/thermal.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/thermal.py @@ -124,7 +124,7 @@ thermal_api_names = [ THERMAL_API_GET_HIGH_THRESHOLD ] -hwsku_dict_thermal = {'ACS-MSN2700': 0, 'LS-SN2700':0, 'ACS-MSN2740': 3, 'ACS-MSN2100': 1, 'ACS-MSN2410': 2, 'ACS-MSN2010': 4, 'ACS-MSN3700': 5, 'ACS-MSN3700C': 6, 'Mellanox-SN2700': 0, 'Mellanox-SN2700-D48C8': 0, 'ACS-MSN3800': 7, 'Mellanox-SN3800-D112C8': 7, 'ACS-MSN4700': 8, 'ACS-MSN3420': 9, 'ACS-MSN4600C': 9} +hwsku_dict_thermal = {'ACS-MSN2700': 0, 'LS-SN2700':0, 'ACS-MSN2740': 3, 'ACS-MSN2100': 1, 'ACS-MSN2410': 2, 'ACS-MSN2010': 4, 'ACS-MSN3700': 5, 'ACS-MSN3700C': 6, 'Mellanox-SN2700': 0, 'Mellanox-SN2700-D48C8': 0, 'ACS-MSN3800': 7, 'Mellanox-SN3800-D112C8': 7, 'ACS-MSN4700': 8, 'ACS-MSN3420': 9, 'ACS-MSN4600C': 10} thermal_profile_list = [ # 2700 { @@ -267,7 +267,7 @@ thermal_profile_list = [ }, # 3420 { - THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4), + 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), diff --git a/platform/mellanox/mlnx-platform-api/tests/mock_platform.py b/platform/mellanox/mlnx-platform-api/tests/mock_platform.py index c534805848..8edb9d4fb1 100644 --- a/platform/mellanox/mlnx-platform-api/tests/mock_platform.py +++ b/platform/mellanox/mlnx-platform-api/tests/mock_platform.py @@ -2,6 +2,7 @@ class MockFan: speed = 60 def __init__(self): self.presence = True + self.name = None self.status = True def get_presence(self): @@ -16,6 +17,9 @@ class MockFan: def get_target_speed(self): return MockFan.speed + def get_name(self): + return self.name + class MockPsu: def __init__(self): diff --git a/platform/mellanox/mlnx-platform-api/tests/test_fan_api.py b/platform/mellanox/mlnx-platform-api/tests/test_fan_api.py index 381260163c..24158997ab 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_fan_api.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_fan_api.py @@ -1,17 +1,57 @@ import os import sys +import pytest from mock import MagicMock +from .mock_platform import MockFan 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 def test_get_absence_fan_direction(): - fan = Fan(True, 0, 0) - fan.get_presence = MagicMock(return_value=False) - assert fan.fan_dir is not None + fan_drawer = RealDrawer(0, DEVICE_DATA['x86_64-mlnx_msn2700-r0']['fans']) + fan = Fan(0, fan_drawer) + fan_drawer.get_presence = MagicMock(return_value=False) + assert not fan.is_psu_fan assert fan.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE + + +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) + fan2 = Fan(1, fan_drawer) + 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) + diff --git a/src/sonic-platform-common b/src/sonic-platform-common index 28c39c5566..75698a8dd8 160000 --- a/src/sonic-platform-common +++ b/src/sonic-platform-common @@ -1 +1 @@ -Subproject commit 28c39c55666dcaef10f62492906c1399eec4ccba +Subproject commit 75698a8dd8f5a9ea2a0e72c5f7e2b3196d22571a