New Platform Management API
Overview
Every platform has a unique set of platform peripheral devices
- PSUs
- Fan modules
- SFP transceiver cages
- Environment sensors
- Front-panel LEDs
- etc.
These devices can vary by manufacturer, model, quantity, interface, ...
Challenge: Attempt to create a standardized, unified API to interface with all combinations of these devices
SONiC Design Principles
-
Unified, standardized behavior
- Consistent experience among all SONiC devices, regardless of underlying platform
- Easy to understand, implement, test and debug
-
Kernel modules kept as simple as possible
- Simply expose hardware registers
- No control logic
-
Peripheral control logic implemented in user-space
- Applications shared among all platforms
- Standardized API defined by SONiC, implemented by vendors
Current Solution
- Individual plugins, one per peripheral device type
- sfputil.py, psuutil.py, eeprom.py, etc.
- SONiC defines base classes, vendors must implement these classes in their plugins
- All Platform-specific plugins live on disk in image, appropriate plugin is loaded at runtime after determining running platform
- Drawbacks
- Lack of structure; Each plugin base class installed as its own Python package
- As we add support for more devices, new plugins are necessary.
- Vendors need to be made aware of new plugins. Not apparent.
- When adding a new abstract method to a base class, one needs to add default implementation to all existing implementations
New Solution
Concept
- Combine all plugin base classes into an object-oriented hierarchy
- Hierarchy based on physical connection of devices
- Object-orientation allows for definition of a generic "DeviceBase" class
- Attributes shared by all/most devices inherited by all (Presence, Model #, Serial #, etc.)
- Installed as one Python package: "sonic_platform_base"
- Vendors implement entire package: "sonic_platform"
- Appropriate platform-specific package will be installed during first boot, similar to platform drivers
- No longer abstract base classes
- All abstract methods simply raise "NotImplementedError"
- Adding new methods to the base classes will not break existing implementations
- To ensure vendors are made aware of new methods, we will add a build-time or run-time test which will output all unimplemented methods
- All abstract methods simply raise "NotImplementedError"
Implementation
- Source for new "sonic_platform" package will reside along with vendor's platform module source under the platform/... directory structure
- At build time, vendor ensures sonic_platform source is compiled into a Python wheel file
- Upon first boot after image installation, at the time the appropriate platform modules are installed, the following must also be done:
- Install the sonic_platform package in the host system
- Copy the sonic_platform Python wheel to the appropriate /usr/share/sonic/device/
/ directory, which gets mounted in the Docker containers
- When the Platform Monitor (PMon) container starts, it will check whether a "sonic_platform" package is installed. If not, it will attempt to install a "sonic_platform*.whl" file in the mounted directory as mentioned above.
- In the host system, applications will interact with the platform API for things like watchdog and reboot cause
- Daemons running in pmon container will be responsible for updating Redis State database with current metrics/status from platfrom hardware
- Command-line utilities in host image will query State DB to retrieve current platform peripheral metrics
- For more real-time data, such as transceiver optical data, a mechanism can be implemented CLI can notify daemons to retrieve data by writing to DB
New Platform API Hierarchy
- Platform
- Chassis
- Base MAC address
- Serial number
- System EEPROM info
- Reboot cause
- Hardware watchdog
- Environment sensors
- Front-panel LEDs
- Status LEDs
- Power supply unit[0 .. p-1]
- Fan[0 .. f-1]
- SFP cage[0 .. s-1]
- Module[0 .. m-1] (Line card, supervisor card, etc.)
- Environment sensors
- Front-panel LEDs
- Status LEDs
- Power supply unit[0 .. p'-1]
- Fan[0 .. f'-1]
- SFP cage[0 .. s'-1]
- Chassis
Sample code to print PSU1 presence using old API
#!/usr/bin/env python
import imp
import subprocess
import sys
# Global platform-specific psuutil class instance
platform_psuutil = None
# Returns platform and HW SKU
def get_platform_and_hwsku():
try:
proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY],
stdout=subprocess.PIPE,
shell=False,
stderr=subprocess.STDOUT)
stdout = proc.communicate()[0]
proc.wait()
platform = stdout.rstrip('\n')
proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY],
stdout=subprocess.PIPE,
shell=False,
stderr=subprocess.STDOUT)
stdout = proc.communicate()[0]
proc.wait()
hwsku = stdout.rstrip('\n')
except OSError, e:
raise OSError("Cannot detect platform")
return (platform, hwsku)
# Loads platform specific psuutil module from source
def load_platform_psuutil():
global platform_psuutil
# Get platform and hwsku
(platform, hwsku) = get_platform_and_hwsku()
# Load platform module from source
platform_path = ''
if len(platform) != 0:
platform_path = "/".join([PLATFORM_ROOT_PATH, platform])
else:
platform_path = PLATFORM_ROOT_PATH_DOCKER
hwsku_path = "/".join([platform_path, hwsku])
try:
module_file = "/".join([platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py"])
module = imp.load_source(PLATFORM_SPECIFIC_MODULE_NAME, module_file)
except IOError, e:
print("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True)
return -1
try:
platform_psuutil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME)
platform_psuutil = platform_psuutil_class()
except AttributeError, e:
print("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)
return -2
return 0
# Load platform-specific psuutil class
err = load_platform_psuutil()
if err != 0:
sys.exit(2)
presence = platform_psuutil.get_psu_presence(1)
print("PSU 1 presence: {}".format(presence))
Sample code to print PSU1 status using new API
#!/usr/bin/env python
import sonic_platform
platform = sonic_platform.platform.Platform()
chassis = platform.get_chassis()
if not chassis:
print("Error getting chassis!")
sys.exit(1)
psu1 = chassis.get_psu(1)
if not psu1:
print("Error getting psu1!")
sys.exit(2)
presence = psu1.get_presence()
print("PSU 1 presence: {}".format(presence))
New Platform API Framework location
https://github.com/sonic-net/sonic-platform-common/tree/master/sonic_platform_base