[Mellanox] Enhancement for fan led management (#4437)

This commit is contained in:
Junchao-Mellanox 2020-05-14 01:01:32 +08:00 committed by GitHub
parent d3c28a45d9
commit 5e6c20481d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 593 additions and 250 deletions

View File

@ -0,0 +1 @@
../x86_64-mlnx_msn2700-r0/thermal_policy.json

View File

@ -0,0 +1 @@
../x86_64-mlnx_msn2700-r0/thermal_policy.json

View File

@ -0,0 +1 @@
../x86_64-mlnx_msn2700-r0/thermal_policy.json

View File

@ -12,10 +12,10 @@ try:
from sonic_platform_base.chassis_base import ChassisBase from sonic_platform_base.chassis_base import ChassisBase
from sonic_platform_base.component_base import ComponentBase from sonic_platform_base.component_base import ComponentBase
from sonic_device_util import get_machine_info 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 sonic_daemon_base.daemon_base import Logger
from os import listdir from os import listdir
from os.path import isfile, join from os.path import isfile, join
from glob import glob
import sys import sys
import io import io
import re import re
@ -61,12 +61,14 @@ class Chassis(ChassisBase):
# Initialize SKU name # Initialize SKU name
self.sku_name = self._get_sku_name() self.sku_name = self._get_sku_name()
self.platform_name = self._get_platform_name()
mi = get_machine_info() mi = get_machine_info()
if mi is not None: if mi is not None:
self.name = mi['onie_platform'] self.name = mi['onie_platform']
self.platform_name = get_platform_info(mi)
else: else:
self.name = self.sku_name self.name = self.sku_name
self.platform_name = self._get_platform_name()
# move the initialization of each components to their dedicated initializer # move the initialization of each components to their dedicated initializer
# which will be called from platform # which will be called from platform
@ -86,36 +88,29 @@ class Chassis(ChassisBase):
# Initialize PSU list # Initialize PSU list
self.psu_module = Psu self.psu_module = Psu
for index in range(MLNX_NUM_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) self._psu_list.append(psu)
def initialize_fan(self): def initialize_fan(self):
from .device_data import DEVICE_DATA
from sonic_platform.fan import Fan from sonic_platform.fan import Fan
from sonic_platform.fan import FAN_PATH from .fan_drawer import RealDrawer, VirtualDrawer
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
# Fan's direction isn't supported on spectrum 1 devices for now fan_data = DEVICE_DATA[self.platform_name]['fans']
mst_dev_list = glob(MST_DEVICE_NAME_PATTERN) drawer_num = fan_data['drawer_num']
if not mst_dev_list: drawer_type = fan_data['drawer_type']
raise RuntimeError("Can't get chip type due to {} not found".format(MST_DEVICE_NAME_PATTERN)) fan_num_per_drawer = fan_data['fan_num_per_drawer']
m = re.search(MST_DEVICE_RE_PATTERN, mst_dev_list[0]) drawer_ctor = RealDrawer if drawer_type == 'real' else VirtualDrawer
if m.group(1) == SPECTRUM1_CHIP_ID: fan_index = 0
has_fan_dir = False for drawer_index in range(drawer_num):
else: drawer = drawer_ctor(drawer_index, fan_data)
has_fan_dir = True self._fan_drawer_list.append(drawer)
for index in range(fan_num_per_drawer):
for index in range(num_of_fan): fan = Fan(fan_index, drawer)
if multi_rotor_in_drawer: fan_index += 1
fan = Fan(has_fan_dir, index, index/2, False, self.platform_name) drawer._fan_list.append(fan)
else: self._fan_list.append(fan)
fan = Fan(has_fan_dir, index, index, False, self.platform_name)
self._fan_list.append(fan)
def initialize_sfp(self): def initialize_sfp(self):

View File

@ -5,6 +5,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:30":13, "31:40":14 , "41:120":15}, "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} "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': { 'x86_64-mlnx_msn2740-r0': {
@ -13,6 +26,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:120":13}, "unk_trust": {"-127:120":13},
"unk_untrust": {"-127:15":13, "16:25":14 , "26:30":15, "31:120":17}, "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': { 'x86_64-mlnx_msn2100-r0': {
@ -21,6 +47,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:40":12, "41:120":13}, "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} "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': { 'x86_64-mlnx_msn2410-r0': {
@ -29,6 +68,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:30":13, "31:40":14 , "41:120":15}, "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} "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': { 'x86_64-mlnx_msn2010-r0': {
@ -37,6 +89,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:120":12}, "unk_trust": {"-127:120":12},
"unk_untrust": {"-127:15":12, "16:20":13 , "21:30":14, "31:35":15, "36:120":16} "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': { 'x86_64-mlnx_msn3700-r0': {
@ -45,6 +110,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:25":12, "26:40":13 , "41:120":14}, "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}, "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': { 'x86_64-mlnx_msn3700c-r0': {
@ -53,6 +131,19 @@ DEVICE_DATA = {
"unk_trust": {"-127:40":12, "41:120":13}, "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}, "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': { 'x86_64-mlnx_msn3800-r0': {
@ -61,14 +152,82 @@ DEVICE_DATA = {
"unk_trust": {"-127:30":12, "31:40":13 , "41:120":14}, "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}, "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': { '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': { 'thermal': {
'minimum_table': { 'minimum_table': {
"unk_trust": {"-127:120":16}, "unk_trust": {"-127:120":16},
"unk_untrust": {"-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
} }
} }
} }

View File

@ -13,25 +13,18 @@ import subprocess
try: try:
from sonic_platform_base.fan_base import FanBase from sonic_platform_base.fan_base import FanBase
from .led import FanLed, ComponentFaultyIndicator
except ImportError as e: except ImportError as e:
raise ImportError (str(e) + "- required module not found") raise ImportError (str(e) + "- required module not found")
LED_ON = '1'
LED_OFF = '0'
PWM_MAX = 255 PWM_MAX = 255
FAN_PATH = "/var/run/hw-management/thermal/" FAN_PATH = "/var/run/hw-management/thermal/"
LED_PATH = "/var/run/hw-management/led/"
CONFIG_PATH = "/var/run/hw-management/config" 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 isn't supported on Spectrum 1. It is supported on Spectrum 2 and later switches
FAN_DIR = "/var/run/hw-management/system/fan_dir" FAN_DIR = "/var/run/hw-management/system/fan_dir"
COOLING_STATE_PATH = "/var/run/hw-management/thermal/cooling_cur_state" 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): class Fan(FanBase):
"""Platform-specific Fan class""" """Platform-specific Fan class"""
@ -44,21 +37,28 @@ class Fan(FanBase):
PSU_FAN_SPEED = ['0x3c', '0x3c', '0x3c', '0x3c', '0x3c', PSU_FAN_SPEED = ['0x3c', '0x3c', '0x3c', '0x3c', '0x3c',
'0x3c', '0x3c', '0x46', '0x50', '0x5a', '0x64'] '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 # API index is starting from 0, Mellanox platform index is starting from 1
self.index = fan_index + 1 self.index = fan_index + 1
self.drawer_index = drawer_index + 1 self.fan_drawer = fan_drawer
self.is_psu_fan = psu_fan 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) self.fan_min_speed_path = "fan{}_min".format(self.index)
if not self.is_psu_fan: if not self.is_psu_fan:
self.fan_speed_get_path = "fan{}_speed_get".format(self.index) self.fan_speed_get_path = "fan{}_speed_get".format(self.index)
self.fan_speed_set_path = "fan{}_speed_set".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.fan_max_speed_path = "fan{}_max".format(self.index)
self._name = "fan{}".format(fan_index + 1) self._name = "fan{}".format(self.index)
else: else:
self.fan_speed_get_path = "psu{}_fan1_speed_get".format(self.index) self.fan_speed_get_path = "psu{}_fan1_speed_get".format(self.index)
self.fan_presence_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.psu_i2c_command_path = os.path.join(CONFIG_PATH, 'fan_command')
self.fan_status_path = "fan{}_fault".format(self.index) 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_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): def get_direction(self):
@ -99,20 +91,10 @@ class Fan(FanBase):
1 stands for forward, in other words intake 1 stands for forward, in other words intake
0 stands for reverse, in other words exhaust 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 return self.FAN_DIRECTION_NOT_APPLICABLE
else:
try: return self.fan_drawer.get_direction()
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)))
def get_name(self): def get_name(self):
return self._name return self._name
@ -150,17 +132,9 @@ class Fan(FanBase):
status = 1 status = 1
else: else:
status = 0 status = 0
return status == 1
else: else:
if self.always_presence: return self.fan_drawer.get_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
def _get_min_speed_in_rpm(self): def _get_min_speed_in_rpm(self):
@ -281,18 +255,6 @@ class Fan(FanBase):
return status 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): def set_status_led(self, color):
""" """
Set led to expected color Set led to expected color
@ -304,48 +266,7 @@ class Fan(FanBase):
Returns: Returns:
bool: True if set success, False if fail. bool: True if set success, False if fail.
""" """
led_cap_list = self._get_led_capability() return self.led.set_status(color)
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
def get_status_led(self): def get_status_led(self):
@ -355,26 +276,7 @@ class Fan(FanBase):
Returns: Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above A string, one of the predefined STATUS_LED_COLOR_* strings above
""" """
led_cap_list = self._get_led_capability() return self.led.get_status()
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
def get_speed_tolerance(self): def get_speed_tolerance(self):

View File

@ -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'

View File

@ -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()

View File

@ -13,11 +13,11 @@ try:
from sonic_platform_base.psu_base import PsuBase from sonic_platform_base.psu_base import PsuBase
from sonic_daemon_base.daemon_base import Logger from sonic_daemon_base.daemon_base import Logger
from sonic_platform.fan import Fan from sonic_platform.fan import Fan
from .led import PsuLed, SharedLed, ComponentFaultyIndicator
from .device_data import DEVICE_DATA
except ImportError as e: except ImportError as e:
raise ImportError (str(e) + "- required module not found") raise ImportError (str(e) + "- required module not found")
LED_ON = '1'
LED_OFF = '0'
# Global logger class instance # Global logger class instance
logger = Logger() logger = Logger()
@ -28,16 +28,11 @@ PSU_CURRENT = "current"
PSU_VOLTAGE = "voltage" PSU_VOLTAGE = "voltage"
PSU_POWER = "power" PSU_POWER = "power"
LED_PATH = "/var/run/hw-management/led/" # in most platforms the file psuX_curr, psuX_volt and psuX_power contain current, voltage and power data respectively.
# 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.
# but there are exceptions which will be handled by the following dictionary # 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 = [ psu_profile_list = [
# default filename convention # default filename convention
{ {
@ -56,9 +51,9 @@ psu_profile_list = [
class Psu(PsuBase): class Psu(PsuBase):
"""Platform-specific Psu class""" """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 global psu_list
PsuBase.__init__(self) PsuBase.__init__(self)
# PSU is 1-based on Mellanox platform # PSU is 1-based on Mellanox platform
@ -66,17 +61,19 @@ class Psu(PsuBase):
psu_list.append(self.index) psu_list.append(self.index)
self.psu_path = "/var/run/hw-management/" self.psu_path = "/var/run/hw-management/"
psu_oper_status = "thermal/psu{}_pwr_status".format(self.index) 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.psu_oper_status = os.path.join(self.psu_path, psu_oper_status)
self._name = "PSU{}".format(psu_index + 1) self._name = "PSU{}".format(psu_index + 1)
if sku in hwsku_dict_psu: if platform in platform_dict_psu:
filemap = psu_profile_list[hwsku_dict_psu[sku]] filemap = psu_profile_list[platform_dict_psu[platform]]
else: else:
filemap = psu_profile_list[0] filemap = psu_profile_list[0]
if sku in hwsku_dict_with_unplugable_psu: self.psu_data = DEVICE_DATA[platform]['psus']
self.always_presence = True
if not self.psu_data['hot_swappable']:
self.always_present = True
self.psu_voltage = None self.psu_voltage = None
self.psu_current = None self.psu_current = None
self.psu_power = None self.psu_power = None
@ -84,7 +81,7 @@ class Psu(PsuBase):
self.psu_temp = None self.psu_temp = None
self.psu_temp_threshold = None self.psu_temp_threshold = None
else: else:
self.always_presence = False self.always_present = False
psu_voltage = filemap[PSU_VOLTAGE].format(self.index) psu_voltage = filemap[PSU_VOLTAGE].format(self.index)
psu_voltage = os.path.join(self.psu_path, psu_voltage) psu_voltage = os.path.join(self.psu_path, psu_voltage)
self.psu_voltage = 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)) self.psu_temp_threshold = os.path.join(self.psu_path, 'thermal/psu{}_temp_max'.format(self.index))
# unplugable PSU has no FAN # unplugable PSU has no FAN
if sku not in hwsku_dict_with_unplugable_psu: if self.psu_data['hot_swappable']:
fan = Fan(False, psu_index, psu_index, True) fan = Fan(psu_index, None, True)
self._fan_list.append(fan) self._fan_list.append(fan)
self.psu_green_led_path = "led_psu_green" if self.psu_data['led_num'] == 1:
self.psu_red_led_path = "led_psu_red" self.led = ComponentFaultyIndicator(Psu.get_shared_led())
self.psu_orange_led_path = "led_psu_orange" else: # 2010/2100
self.psu_led_cap_path = "led_psu_capability" self.led = PsuLed(self.index)
def get_name(self): def get_name(self):
@ -151,8 +148,8 @@ class Psu(PsuBase):
Returns: Returns:
bool: True if PSU is present, False if not bool: True if PSU is present, False if not
""" """
if self.always_presence: if self.always_present:
return self.always_presence return self.always_present
else: else:
status = self._read_generic_file(self.psu_presence, 0) status = self._read_generic_file(self.psu_presence, 0)
return status == 1 return status == 1
@ -199,19 +196,6 @@ class Psu(PsuBase):
else: else:
return None 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): def set_status_led(self, color):
""" """
Sets the state of the PSU status LED Sets the state of the PSU status LED
@ -226,45 +210,7 @@ class Psu(PsuBase):
Notes: Notes:
Only one led for all PSUs. Only one led for all PSUs.
""" """
led_cap_list = self._get_led_capability() return self.led.set_status(color)
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
def get_status_led(self): def get_status_led(self):
@ -274,26 +220,10 @@ class Psu(PsuBase):
Returns: Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above A string, one of the predefined STATUS_LED_COLOR_* strings above
""" """
led_cap_list = self._get_led_capability() if self.psu_data['led_num'] == 1:
if led_cap_list is None: return Psu.get_shared_led().get_status()
return self.STATUS_LED_COLOR_OFF else:
return self.led.get_status()
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
def get_power_available_status(self): def get_power_available_status(self):
@ -312,6 +242,12 @@ class Psu(PsuBase):
else: else:
return True, "" 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): def get_temperature(self):
""" """
Retrieves current temperature reading from PSU Retrieves current temperature reading from PSU
@ -367,4 +303,3 @@ class Psu(PsuBase):
""" """
# hw-management doesn't expose those sysfs for now # hw-management doesn't expose those sysfs for now
raise NotImplementedError raise NotImplementedError

View File

@ -124,7 +124,7 @@ thermal_api_names = [
THERMAL_API_GET_HIGH_THRESHOLD 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 = [ thermal_profile_list = [
# 2700 # 2700
{ {
@ -267,7 +267,7 @@ thermal_profile_list = [
}, },
# 3420 # 3420
{ {
THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4), THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2),
THERMAL_DEV_CATEGORY_MODULE:(1, 60), THERMAL_DEV_CATEGORY_MODULE:(1, 60),
THERMAL_DEV_CATEGORY_PSU:(1, 2), THERMAL_DEV_CATEGORY_PSU:(1, 2),
THERMAL_DEV_CATEGORY_CPU_PACK:(0,1), THERMAL_DEV_CATEGORY_CPU_PACK:(0,1),

View File

@ -2,6 +2,7 @@ class MockFan:
speed = 60 speed = 60
def __init__(self): def __init__(self):
self.presence = True self.presence = True
self.name = None
self.status = True self.status = True
def get_presence(self): def get_presence(self):
@ -16,6 +17,9 @@ class MockFan:
def get_target_speed(self): def get_target_speed(self):
return MockFan.speed return MockFan.speed
def get_name(self):
return self.name
class MockPsu: class MockPsu:
def __init__(self): def __init__(self):

View File

@ -1,17 +1,57 @@
import os import os
import sys import sys
import pytest
from mock import MagicMock from mock import MagicMock
from .mock_platform import MockFan
test_path = os.path.dirname(os.path.abspath(__file__)) test_path = os.path.dirname(os.path.abspath(__file__))
modules_path = os.path.dirname(test_path) modules_path = os.path.dirname(test_path)
sys.path.insert(0, modules_path) sys.path.insert(0, modules_path)
from sonic_platform.fan import Fan 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(): def test_get_absence_fan_direction():
fan = Fan(True, 0, 0) fan_drawer = RealDrawer(0, DEVICE_DATA['x86_64-mlnx_msn2700-r0']['fans'])
fan.get_presence = MagicMock(return_value=False) fan = Fan(0, fan_drawer)
assert fan.fan_dir is not None fan_drawer.get_presence = MagicMock(return_value=False)
assert not fan.is_psu_fan assert not fan.is_psu_fan
assert fan.get_direction() == Fan.FAN_DIRECTION_NOT_APPLICABLE 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)

@ -1 +1 @@
Subproject commit 28c39c55666dcaef10f62492906c1399eec4ccba Subproject commit 75698a8dd8f5a9ea2a0e72c5f7e2b3196d22571a