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 c9f2f46d31..4b4eed5bbb 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py @@ -184,6 +184,12 @@ class DeviceDataManager: from sonic_py_common import device_info return device_info.get_platform() + @classmethod + @utils.read_only_cache() + def is_simx_platform(cls): + platform_name = cls.get_platform_name() + return platform_name and 'simx' in platform_name + @classmethod @utils.read_only_cache() def get_fan_drawer_count(cls): diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/led.py b/platform/mellanox/mlnx-platform-api/sonic_platform/led.py index 49c243fd80..50c8f79803 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/led.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/led.py @@ -19,105 +19,130 @@ import time from sonic_py_common.logger import Logger from . import utils +from . import device_data logger = Logger() + class Led(object): STATUS_LED_COLOR_GREEN = 'green' - STATUS_LED_COLOR_GREEN_BLINK = 'green_blink' STATUS_LED_COLOR_RED = 'red' - STATUS_LED_COLOR_RED_BLINK = 'red_blink' STATUS_LED_COLOR_ORANGE = 'orange' - STATUS_LED_COLOR_ORANGE_BLINK = 'orange_blink' STATUS_LED_COLOR_OFF = 'off' + STATUS_LED_COLOR_GREEN_BLINK = 'green_blink' + STATUS_LED_COLOR_RED_BLINK = 'red_blink' + STATUS_LED_COLOR_ORANGE_BLINK = 'orange_blink' LED_ON = '255' LED_OFF = '0' LED_BLINK = '50' + SIMILAR_COLORS = { + 'red': ('amber', 'orange'), + 'amber': ('red', 'orange'), + 'orange': ('red', 'amber') + } + + PRIMARY_COLORS = { + 'red': 'red', + 'amber': 'red', + 'orange': 'red', + 'green': 'green' + } + LED_PATH = "/var/run/hw-management/led/" + def __init__(self): + self.supported_colors = set() + self.supported_blinks = set() + + def _get_actual_color(self, color): + # Different platform has different LED capability, this capability should be + # transparent for upper layer. So, here is the logic to help find actual color + # if the given color is not supported. + if color not in self.supported_colors: + return self._get_similar_color(color) + return color + + def _get_similar_color(self, color): + # If a given color is not supported, we try to find a similar color from + # canditates + similar_colors = self.SIMILAR_COLORS.get(color) + if similar_colors: + for actual_color in similar_colors: + if actual_color in self.supported_colors: + return actual_color + return None + + def _get_primary_color(self, color): + # For backward compatible, we don't return the actual color here. + # We always return "green"(indicate a good status) or "red"(indicate a bad status) + # which are the "primary" colors. + return self.PRIMARY_COLORS.get(color, color) + + def _get_actual_blink_color(self, blink_color): + # Different platform has different LED capability, this capability should be + # transparent for upper layer. So, here is the logic to help find actual blink color + # if the given blink color is not supported. + if blink_color not in self.supported_blinks: + return self._get_similar_blink_color(blink_color) + return blink_color + + def _get_similar_blink_color(self, color): + # If a given blink color is not supported, we try to find a similar blink color from + # canditates + similar_colors = self.SIMILAR_COLORS.get(color) + if similar_colors: + for actual_color in similar_colors: + if actual_color in self.supported_blinks: + return actual_color + return None + def set_status(self, color): - led_cap_list = self.get_capability() - if led_cap_list is None: + self.get_capability() + if not self.supported_colors: + if not device_data.DeviceDataManager.is_simx_platform(): + logger.log_error(f'Failed to get LED capability for {self._led_id} LED') return False status = False try: - self._stop_blink(led_cap_list) - blink_pos = color.find('blink') + self._stop_blink() + blink_pos = color.find('_blink') if blink_pos != -1: - return self._set_status_blink(color, led_cap_list) + return self._set_status_blink(color[0:blink_pos]) - if color == Led.STATUS_LED_COLOR_GREEN: - 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 - 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: + if color != Led.STATUS_LED_COLOR_OFF: + actual_color = self._get_actual_color(color) + if not actual_color: + logger.log_error(f'Set LED to color {color} is not supported') return False - utils.write_file(led_path, Led.LED_ON) - status = True - elif color == Led.STATUS_LED_COLOR_ORANGE: - if Led.STATUS_LED_COLOR_ORANGE in led_cap_list: - led_path = self.get_orange_led_path() - elif Led.STATUS_LED_COLOR_RED in led_cap_list: - led_path = self.get_red_led_path() - else: - return False - - 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: - utils.write_file(self.get_green_led_path(), Led.LED_OFF) - if Led.STATUS_LED_COLOR_RED in led_cap_list: - utils.write_file(self.get_red_led_path(), Led.LED_OFF) - if Led.STATUS_LED_COLOR_ORANGE in led_cap_list: - utils.write_file(self.get_orange_led_path(), Led.LED_OFF) - + utils.write_file(self.get_led_path(actual_color), Led.LED_ON) status = True else: - status = False + for color in self.supported_colors: + utils.write_file(self.get_led_path(color), Led.LED_OFF) + + status = True except (ValueError, IOError): status = False return status - 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 - elif color == Led.STATUS_LED_COLOR_ORANGE_BLINK and Led.STATUS_LED_COLOR_RED_BLINK in led_cap_list: - color = Led.STATUS_LED_COLOR_RED_BLINK - else: - return False - - if Led.STATUS_LED_COLOR_GREEN_BLINK == color: - self._trigger_blink(self.get_green_led_trigger()) - 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._trigger_blink(self.get_red_led_trigger()) - 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._trigger_blink(self.get_orange_led_trigger()) - return self._set_led_blink_status(self.get_orange_led_delay_on_path(), self.get_orange_led_delay_off_path(), Led.LED_BLINK) - else: + def _set_status_blink(self, color): + actual_color = self._get_actual_blink_color(color) + if not actual_color: + logger.log_error(f'Set LED to color {color}_blink is not supported') return False - def _stop_blink(self, led_cap_list): + self._trigger_blink(self.get_led_trigger(actual_color)) + return self._set_led_blink_status(actual_color) + + def _stop_blink(self): try: - if Led.STATUS_LED_COLOR_GREEN_BLINK in led_cap_list: - self._untrigger_blink(self.get_green_led_trigger()) - if Led.STATUS_LED_COLOR_RED_BLINK in led_cap_list: - self._untrigger_blink(self.get_red_led_trigger()) - if Led.STATUS_LED_COLOR_ORANGE_BLINK in led_cap_list: - self._untrigger_blink(self.get_orange_led_trigger()) + for color in self.supported_colors: + self._untrigger_blink(self.get_led_trigger(color)) except Exception as e: return @@ -127,12 +152,14 @@ class Led(object): def _untrigger_blink(self, blink_trigger_file): utils.write_file(blink_trigger_file, 'none') - def _set_led_blink_status(self, delay_on_file, delay_off_file, value): + def _set_led_blink_status(self, actual_color): + delay_on_file = self.get_led_delay_on_path(actual_color) + delay_off_file = self.get_led_delay_off_path(actual_color) if not self._wait_files_ready((delay_on_file, delay_off_file)): return False - utils.write_file(delay_on_file, value) - utils.write_file(delay_off_file, value) + utils.write_file(delay_on_file, Led.LED_BLINK) + utils.write_file(delay_off_file, Led.LED_BLINK) return True def _wait_files_ready(self, file_list): @@ -155,97 +182,76 @@ class Led(object): return False def get_status(self): - led_cap_list = self.get_capability() - if led_cap_list is None: + self.get_capability() + if not self.supported_colors: + if not device_data.DeviceDataManager.is_simx_platform(): + logger.log_error(f'Failed to get LED capability for {self._led_id} LED') return Led.STATUS_LED_COLOR_OFF try: - blink_status = self._get_blink_status(led_cap_list) + blink_status = self._get_blink_status() if blink_status is not None: return blink_status - if utils.read_str_from_file(self.get_green_led_path()) != Led.LED_OFF: - return Led.STATUS_LED_COLOR_GREEN + actual_color = None + for color in self.supported_colors: + if utils.read_str_from_file(self.get_led_path(color)) != Led.LED_OFF: + actual_color = color + break - if Led.STATUS_LED_COLOR_RED in led_cap_list: - 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: - if utils.read_str_from_file(self.get_orange_led_path()) != Led.LED_OFF: - return Led.STATUS_LED_COLOR_RED + if actual_color is not None: + return self._get_primary_color(actual_color) 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_blink_status(self, led_cap_list): + def _get_blink_status(self): try: - 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 - if Led.STATUS_LED_COLOR_ORANGE_BLINK in led_cap_list: - if self._is_led_blinking(self.get_orange_led_delay_on_path(), self.get_orange_led_delay_off_path()): - return Led.STATUS_LED_COLOR_ORANGE_BLINK + for color in self.supported_colors: + if self._is_led_blinking(color): + return f'{color}_blink' except Exception as e: return None return None - def _is_led_blinking(self, delay_on_file, delay_off_file): - 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) + def _is_led_blinking(self, color): + delay_on = utils.read_str_from_file(self.get_led_delay_on_path(color), default=Led.LED_OFF, log_func=None) + delay_off = utils.read_str_from_file(self.get_led_delay_off_path(color), default=Led.LED_OFF, log_func=None) return delay_on != Led.LED_OFF and delay_off != Led.LED_OFF def get_capability(self): caps = utils.read_str_from_file(self.get_led_cap_path()) - return set(caps.split()) + for capability in caps.split(): + if capability == 'none': + continue - def get_green_led_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_green'.format(self._led_id)) + pos = capability.find('_blink') + if pos != -1: + self.supported_blinks.add(capability[0:pos]) + else: + self.supported_colors.add(capability) - def get_green_led_delay_off_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_green_delay_off'.format(self._led_id)) + def get_led_path(self, color): + return os.path.join(Led.LED_PATH, f'led_{self._led_id}_{color}') - def get_green_led_delay_on_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_green_delay_on'.format(self._led_id)) + def get_led_trigger(self, color): + return os.path.join(Led.LED_PATH, f'led_{self._led_id}_{color}_trigger') - def get_green_led_trigger(self): - return os.path.join(Led.LED_PATH, 'led_{}_green_trigger'.format(self._led_id)) + def get_led_delay_off_path(self, color): + return os.path.join(Led.LED_PATH, f'led_{self._led_id}_{color}_delay_off') - def get_red_led_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_red'.format(self._led_id)) - - def get_red_led_delay_off_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_red_delay_off'.format(self._led_id)) - - def get_red_led_delay_on_path(self): - 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): - return os.path.join(Led.LED_PATH, 'led_{}_orange'.format(self._led_id)) - - def get_orange_led_delay_off_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_orange_delay_off'.format(self._led_id)) - - def get_orange_led_delay_on_path(self): - 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_delay_on_path(self, color): + return os.path.join(Led.LED_PATH, f'led_{self._led_id}_{color}_delay_on') def get_led_cap_path(self): - return os.path.join(Led.LED_PATH, 'led_{}_capability'.format(self._led_id)) + return os.path.join(Led.LED_PATH, f'led_{self._led_id}_capability') class FanLed(Led): def __init__(self, index): + super().__init__() if index is not None: self._led_id = 'fan{}'.format(index) else: @@ -254,6 +260,7 @@ class FanLed(Led): class PsuLed(Led): def __init__(self, index): + super().__init__() if index is not None: self._led_id = 'psu{}'.format(index) else: @@ -262,10 +269,13 @@ class PsuLed(Led): class SystemLed(Led): def __init__(self): + super().__init__() self._led_id = 'status' class SharedLed(object): + # for shared LED, blink is not supported for now. Currently, only PSU and fan LED + # might be shared LED, and there is no requirement to set PSU/fan LED to blink status. LED_PRIORITY = { Led.STATUS_LED_COLOR_RED: 0, Led.STATUS_LED_COLOR_GREEN: 1 diff --git a/platform/mellanox/mlnx-platform-api/tests/test_led.py b/platform/mellanox/mlnx-platform-api/tests/test_led.py index 1544ae35fb..50126f19a9 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_led.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_led.py @@ -57,45 +57,45 @@ class TestLed: 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 + mock_file_content[physical_led.get_led_path('green')] = 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 + mock_file_content[physical_led.get_led_path('red')] = 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 + mock_file_content[physical_led.get_led_path('green')] = Led.LED_OFF assert obj.set_status_led(Led.STATUS_LED_COLOR_ORANGE) is True assert obj.get_status_led() == Led.STATUS_LED_COLOR_RED - mock_file_content[physical_led.get_orange_led_path()] = Led.LED_OFF + mock_file_content[physical_led.get_led_path('orange')] = Led.LED_OFF 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 + mock_file_content[physical_led.get_led_delay_off_path('red')] = Led.LED_OFF + mock_file_content[physical_led.get_led_delay_on_path('red')] = 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 + mock_file_content[physical_led.get_led_delay_off_path('green')] = Led.LED_OFF + mock_file_content[physical_led.get_led_delay_on_path('green')] = 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 + mock_file_content[physical_led.get_led_delay_off_path('green')] = Led.LED_OFF + mock_file_content[physical_led.get_led_delay_on_path('green')] = 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_path('green'): Led.LED_ON, + led.get_led_path('red'): Led.LED_OFF, + led.get_led_path('orange'): Led.LED_OFF, led.get_led_cap_path(): 'none green green_blink red red_blink orange', - 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, + led.get_led_delay_off_path('green'): Led.LED_OFF, + led.get_led_delay_on_path('green'): Led.LED_OFF, + led.get_led_delay_off_path('red'): Led.LED_OFF, + led.get_led_delay_on_path('red'): Led.LED_OFF, + led.get_led_delay_off_path('orange'): Led.LED_OFF, + led.get_led_delay_on_path('orange'): Led.LED_OFF, } @mock.patch('sonic_platform.led.Led._wait_files_ready', mock.MagicMock(return_value=True)) @@ -124,19 +124,20 @@ class TestLed: 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 + mock_file_content[physical_led.get_led_path('green')] = 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 + mock_file_content[physical_led.get_led_path('red')] = 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 + mock_file_content[physical_led.get_led_path('red')] = Led.LED_OFF 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 + assert obj2.get_status_led() == Led.STATUS_LED_COLOR_GREEN @mock.patch('sonic_platform.led.Led._wait_files_ready', mock.MagicMock(return_value=True)) def test_psu_led(self):