Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sonic_led/led_control_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
except ImportError as e:
raise ImportError (str(e) + " - required module not found")

#
# Platform API V1 (Deprecated)
# This class is maintained for backward compatibility. New platforms should
# implement Platform API V2 (sonic_platform_base.led_base.LedBase).
#
class LedControlBase(object):
__metaclass__ = abc.ABCMeta

Expand Down
93 changes: 93 additions & 0 deletions sonic_platform_base/led_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
led_base.py

Abstract base class for implementing a platform-specific class with which
to interact with a LED in SONiC
"""

try:
from enum import Enum
except ImportError as e:
raise ImportError(str(e) + " - required module not found") from e

class LedColor(Enum):
"""
Enumeration of LED colors

These are the standard colors used by LEDs.
"""
OFF = "off"
GREEN = "green"
AMBER = "amber"
YELLOW = "amber" # YELLOW is an alias for AMBER
RED = "red"
BLUE = "blue"

class LedBase:
"""
Abstract base class for interfacing with an LED

This class represents a single physical LED that can be controlled.
"""

def __init__(self):
pass

def get_name(self):
"""
Retrieves the name of the LED

Returns:
A string representing the name of the LED. This is a platform-specific
identifier that can be used for debugging purposes.

Example:
"port1_led1", "osfp1_led2"
"""
raise NotImplementedError

def get_color_capabilities(self):
"""
Retrieves the color capabilities of the LED

Returns:
A list of LedColor enum values representing the colors this LED
can display. Platforms should include OFF in the capability list
for clarity and completeness.

Example:
[LedColor.OFF, LedColor.GREEN, LedColor.AMBER]
[LedColor.OFF, LedColor.GREEN, LedColor.RED, LedColor.BLUE]
"""
raise NotImplementedError

def set_color(self, color):
"""
Sets the color of the LED

Args:
color: A LedColor enum value representing the desired color.
The color must be one of the values returned by
get_color_capabilities()

Returns:
A boolean, True if the color was set successfully, False if not

Raises:
ValueError: if the color is not supported by this LED

Example:
led.set_color(LedColor.GREEN) # Set LED to green
led.set_color(LedColor.AMBER) # Set LED to amber
led.set_color(LedColor.OFF) # Turn LED off
"""
raise NotImplementedError

def get_color(self):
"""
Retrieves the current color of the LED (optional method)

Returns:
A LedColor enum value representing the current color of the LED
"""
raise NotImplementedError
50 changes: 50 additions & 0 deletions sonic_platform_base/sfp_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ def __init__(self, bank=0):
# List of ThermalBase-derived objects representing all thermals
# available on the SFP
self._thermal_list = []
# List of all LEDs associated with SFP port
self._led_list = []
self._bank = bank
self._xcvr_api_factory = XcvrApiFactory(self.read_eeprom, self.write_eeprom)
self._xcvr_api = None
Expand Down Expand Up @@ -124,6 +126,54 @@ def get_thermal(self, index):

return thermal

def get_num_leds(self):
"""
Retrieves the number of LEDs available for this SFP port

Returns:
An integer, the number of LEDs available on this SFP port
Returns 0 if no controllable LEDs are available
"""
return len(self._led_list)

def get_all_leds(self):
"""
Retrieves all LEDs available for this SFP port

Returns:
A list of objects derived from LedBase representing all LEDs
available on this SFP port cage. The list is ordered with index 0
being the leftmost LED (for horizontal orientation) or topmost LED
(for vertical orientation).
Returns empty list [] if no controllable LEDs are available

Platforms are responsible for implementing this method if they want the
front-panel LED control daemon (ledd) to use platform LED policy V2.
"""
raise NotImplementedError

def get_led(self, index):
"""
Retrieves LED represented by (0-based) index

Args:
index: An integer, the index (0-based) of the LED to retrieve.
Index 0 represents the leftmost LED (horizontal) or
topmost LED (vertical orientation)

Returns:
An object derived from LedBase representing the specified LED
"""
led = None

try:
led = self._led_list[index]
except IndexError:
sys.stderr.write("LED index {} out of range (0-{})\n".format(
index, len(self._led_list)-1))

return led

def get_transceiver_info(self):
"""
Retrieves transceiver info of this SFP
Expand Down
66 changes: 66 additions & 0 deletions tests/led_base_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'''
Test LedBase / LedColor module
'''

import pytest

from sonic_platform_base.led_base import LedBase, LedColor


class TestLedColor:
'''
Collection of LedColor tests
'''

@staticmethod
def test_color_values():
'''
Each color maps to its expected string value.
'''
assert LedColor.OFF.value == 'off'
assert LedColor.GREEN.value == 'green'
assert LedColor.AMBER.value == 'amber'
assert LedColor.RED.value == 'red'
assert LedColor.BLUE.value == 'blue'

@staticmethod
def test_yellow_is_alias_of_amber():
'''
YELLOW shares AMBER's value, so Python's Enum makes it an alias:
the two members are the same object.
'''
assert LedColor.YELLOW is LedColor.AMBER
assert LedColor.YELLOW.value == 'amber'


class TestLedBase:
'''
Collection of LedBase tests
'''

@staticmethod
def test_unimplemented_methods_raise():
'''
All abstract methods raise NotImplementedError by default.
'''
led = LedBase()
not_implemented_methods = [
(led.get_name,),
(led.get_color_capabilities,),
(led.set_color, LedColor.GREEN),
(led.get_color,),
]

for method in not_implemented_methods:
func = method[0]
args = method[1:]
with pytest.raises(NotImplementedError):
func(*args)

@staticmethod
def test_init_no_args():
'''
Default constructor takes no args and does not raise.
'''
# Should not raise
LedBase()
78 changes: 78 additions & 0 deletions tests/sfp_base_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'''
Test SfpBase LED-related methods
'''

import pytest
from unittest import mock

from sonic_platform_base.sfp_base import SfpBase
from sonic_platform_base.led_base import LedBase

def _make_led(name='led0'):
led = mock.MagicMock(spec=LedBase)
led.get_name.return_value = name
return led

class TestSfpBaseLeds:
'''
Coverage for the LED accessors added by the platform LED policy V2 patch.
'''

@staticmethod
def test_led_list_initialized_empty():
'''
SfpBase.__init__ must initialize self._led_list to an empty list so
that platforms inheriting the default get_num_leds() / get_led()
behavior do not blow up before they populate it.
'''
sfp = SfpBase()
assert sfp._led_list == []
assert sfp.get_num_leds() == 0

@staticmethod
def test_leds_with_populated_list():
'''
Returns the correct count and get_led(index) returns the matching LED
'''
sfp = SfpBase()
leds = [_make_led('a'), _make_led('b'), _make_led('c')]
sfp._led_list = leds

assert sfp.get_num_leds() == 3
assert sfp.get_led(0) is leds[0]
assert sfp.get_led(1) is leds[1]
assert sfp.get_led(2) is leds[2]

@staticmethod
def test_get_all_leds_raises_by_default():
'''
get_all_leds() raises NotImplementedError on the base class. This is
the platform's opt-in to LED policy V2
'''
sfp = SfpBase()
with pytest.raises(NotImplementedError):
sfp.get_all_leds()

@staticmethod
def test_get_led_out_of_range_returns_none(capsys):
'''
Out-of-range indexes return None and write a diagnostic to stderr
(matching the get_thermal() pattern).
'''
sfp = SfpBase()
sfp._led_list = [_make_led('a')]

assert sfp.get_led(5) is None
captured = capsys.readouterr()
assert 'LED index 5 out of range' in captured.err

@staticmethod
def test_get_led_empty_list_returns_none(capsys):
'''
With no LEDs registered, any index is out of range.
'''
sfp = SfpBase()

assert sfp.get_led(0) is None
captured = capsys.readouterr()
assert 'LED index 0 out of range' in captured.err
Loading