9deca05f9d
- Why I did it Currently, LED sysfs path is hardcoded. We will need change LED code if new LED color is supported for new platforms. This PR is aimed to improve this. By this PR, LED sysfs path is deduced from LED capability file. - How I did it Improve LED management on Nvidia platform: get LED capability from capability file and deduce sysfs name according to the capability - How to verify it Unit test Manual test
325 lines
11 KiB
Python
325 lines
11 KiB
Python
#
|
|
# Copyright (c) 2020-2021 NVIDIA CORPORATION & AFFILIATES.
|
|
# Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
import os
|
|
import 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_RED = 'red'
|
|
STATUS_LED_COLOR_ORANGE = 'orange'
|
|
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):
|
|
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()
|
|
blink_pos = color.find('_blink')
|
|
if blink_pos != -1:
|
|
return self._set_status_blink(color[0:blink_pos])
|
|
|
|
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(self.get_led_path(actual_color), Led.LED_ON)
|
|
status = True
|
|
else:
|
|
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):
|
|
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
|
|
|
|
self._trigger_blink(self.get_led_trigger(actual_color))
|
|
return self._set_led_blink_status(actual_color)
|
|
|
|
def _stop_blink(self):
|
|
try:
|
|
for color in self.supported_colors:
|
|
self._untrigger_blink(self.get_led_trigger(color))
|
|
except Exception as e:
|
|
return
|
|
|
|
def _trigger_blink(self, blink_trigger_file):
|
|
utils.write_file(blink_trigger_file, 'timer')
|
|
|
|
def _untrigger_blink(self, blink_trigger_file):
|
|
utils.write_file(blink_trigger_file, 'none')
|
|
|
|
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, Led.LED_BLINK)
|
|
utils.write_file(delay_off_file, Led.LED_BLINK)
|
|
return True
|
|
|
|
def _wait_files_ready(self, file_list):
|
|
"""delay_off and delay_on sysfs will be available only if _trigger_blink is called. And once
|
|
_trigger_blink is called, driver might need time to prepare delay_off and delay_on. So,
|
|
need wait a few seconds here to make sure the sysfs is ready
|
|
|
|
Args:
|
|
file_list (list of str): files to be checked
|
|
"""
|
|
wait_time = 5.0
|
|
initial_sleep = 0.01
|
|
while wait_time > 0:
|
|
if all([os.path.exists(x) for x in file_list]):
|
|
return True
|
|
time.sleep(initial_sleep)
|
|
wait_time -= initial_sleep
|
|
initial_sleep = initial_sleep * 2
|
|
|
|
return False
|
|
|
|
def get_status(self):
|
|
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()
|
|
if blink_status is not None:
|
|
return blink_status
|
|
|
|
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 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):
|
|
try:
|
|
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, 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())
|
|
for capability in caps.split():
|
|
if capability == 'none':
|
|
continue
|
|
|
|
pos = capability.find('_blink')
|
|
if pos != -1:
|
|
self.supported_blinks.add(capability[0:pos])
|
|
else:
|
|
self.supported_colors.add(capability)
|
|
|
|
def get_led_path(self, color):
|
|
return os.path.join(Led.LED_PATH, f'led_{self._led_id}_{color}')
|
|
|
|
def get_led_trigger(self, color):
|
|
return os.path.join(Led.LED_PATH, f'led_{self._led_id}_{color}_trigger')
|
|
|
|
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_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, 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:
|
|
self._led_id = 'fan'
|
|
|
|
|
|
class PsuLed(Led):
|
|
def __init__(self, index):
|
|
super().__init__()
|
|
if index is not None:
|
|
self._led_id = 'psu{}'.format(index)
|
|
else:
|
|
self._led_id = 'psu'
|
|
|
|
|
|
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
|
|
}
|
|
|
|
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:
|
|
try:
|
|
if SharedLed.LED_PRIORITY[virtual_led.get_led_color()] < SharedLed.LED_PRIORITY[target_color]:
|
|
target_color = virtual_led.get_led_color()
|
|
except KeyError:
|
|
return False
|
|
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):
|
|
current_color = self._color
|
|
self._color = color
|
|
if self._shared_led.update_status_led():
|
|
return True
|
|
else:
|
|
self._color = current_color
|
|
return False
|
|
|
|
def get_led_color(self):
|
|
return self._color
|
|
|
|
def get_status(self):
|
|
return self._shared_led.get_status()
|