Skip to content

Commit

Permalink
[psuutil] Refactor to utilize sonic-platform package (sonic-net#1434)
Browse files Browse the repository at this point in the history
#### What I did

Refactor psuutil to use sonic-platform package in lieu of old, deprecated platform plugins.

The psuutil utility is still useful, as psushow only reads and displays PSU data from State DB. However, this utility provides us the ability to read directly from the PSUs which is helpful for debugging.

#### How I did it

- Complete refactor to use sonic-platform package
- Add more output columns to display (Model, Serial, Voltage, Current, Power)
- Bump version to 2.0
- Add basic unit tests
  • Loading branch information
jleveque authored Feb 16, 2021
1 parent cf577cc commit 310d203
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 68 deletions.
152 changes: 84 additions & 68 deletions psuutil/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,46 @@
# Command-line utility for interacting with PSU in SONiC
#

try:
import imp
import os
import sys
import os
import sys

import click
from sonic_py_common import device_info, logger
from tabulate import tabulate
except ImportError as e:
raise ImportError("%s - required module not found" % str(e))
import click
import sonic_platform
from sonic_py_common import logger
from tabulate import tabulate

VERSION = '1.0'

VERSION = '2.0'

SYSLOG_IDENTIFIER = "psuutil"
PLATFORM_SPECIFIC_MODULE_NAME = "psuutil"
PLATFORM_SPECIFIC_CLASS_NAME = "PsuUtil"

# Global platform-specific psuutil class instance
platform_psuutil = None
ERROR_PERMISSIONS = 1
ERROR_CHASSIS_LOAD = 2
ERROR_NOT_IMPLEMENTED = 3

# Global platform-specific Chassis class instance
platform_chassis = None

# Global logger instance
log = logger.Logger(SYSLOG_IDENTIFIER)


# ==================== Methods for initialization ====================

# Loads platform specific psuutil module from source
def load_platform_psuutil():
global platform_psuutil

# Load platform module from source
platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs()
# Instantiate platform-specific Chassis class
def load_platform_chassis():
global platform_chassis

# Load new platform api class
try:
module_file = os.path.join(platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py")
module = imp.load_source(PLATFORM_SPECIFIC_MODULE_NAME, module_file)
except IOError as e:
log.log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True)
return -1
platform_chassis = sonic_platform.platform.Platform().get_chassis()
except Exception as e:
log.log_error("Failed to instantiate Chassis due to {}".format(repr(e)))

try:
platform_psuutil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME)
platform_psuutil = platform_psuutil_class()
except AttributeError as e:
log.log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)
return -2
if not platform_chassis:
return False

return 0
return True


# ==================== CLI commands and groups ====================
Expand All @@ -66,63 +57,88 @@ def cli():

if os.geteuid() != 0:
click.echo("Root privileges are required for this operation")
sys.exit(1)
sys.exit(ERROR_PERMISSIONS)

# Load platform-specific psuutil class
err = load_platform_psuutil()
if err != 0:
sys.exit(2)

# 'version' subcommand
# Load platform-specific Chassis class
if not load_platform_chassis():
sys.exit(ERROR_CHASSIS_LOAD)


# 'version' subcommand
@cli.command()
def version():
"""Display version info"""
click.echo("psuutil version {0}".format(VERSION))

# 'numpsus' subcommand


# 'numpsus' subcommand
@cli.command()
def numpsus():
"""Display number of supported PSUs on device"""
click.echo(str(platform_psuutil.get_num_psus()))

# 'status' subcommand
num_psus = platform_chassis.get_num_psus()
click.echo(str(num_psus))


# 'status' subcommand
@cli.command()
@click.option('-i', '--index', default=-1, type=int, help="the index of PSU")
@click.option('-i', '--index', default=-1, type=int, help='Index of the PSU')
def status(index):
"""Display PSU status"""
supported_psu = list(range(1, platform_psuutil.get_num_psus() + 1))
psu_ids = []
if (index < 0):
psu_ids = supported_psu
else:
psu_ids = [index]

header = ['PSU', 'Status']
header = ['PSU', 'Model', 'Serial', 'Voltage (V)', 'Current (A)', 'Power (W)', 'Status', 'LED']
status_table = []

for psu in psu_ids:
msg = ""
psu_name = "PSU {}".format(psu)
if psu not in supported_psu:
click.echo("Error! The {} is not available on the platform.\n"
"Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus()))
continue
presence = platform_psuutil.get_psu_presence(psu)
if presence:
oper_status = platform_psuutil.get_psu_status(psu)
msg = 'OK' if oper_status else "NOT OK"
else:
msg = 'NOT PRESENT'
status_table.append([psu_name, msg])
psu_list = platform_chassis.get_all_psus()

for psu in psu_list:
psu_name = psu.get_name()
status = 'NOT PRESENT'
model = 'N/A'
serial = 'N/A'
voltage = 'N/A'
current = 'N/A'
power = 'N/A'
led_color = 'N/A'

if psu.get_presence():
try:
status = 'OK' if psu.get_powergood_status() else 'NOT OK'
except NotImplementedError:
status = 'UNKNOWN'

try:
model = psu.get_model()
except NotImplementedError:
pass

try:
serial = psu.get_serial()
except NotImplementedError:
pass

try:
voltage = psu.get_voltage()
except NotImplementedError:
pass

try:
current = psu.get_current()
except NotImplementedError:
pass

try:
power = psu.get_power()
except NotImplementedError:
pass

try:
led_color = psu.get_status_led()
except NotImplementedError:
pass

status_table.append([psu_name, model, serial, voltage, current, power, status, led_color])

if status_table:
click.echo(tabulate(status_table, header, tablefmt="simple"))
click.echo(tabulate(status_table, header, tablefmt='simple', floatfmt='.2f'))


if __name__ == '__main__':
Expand Down
21 changes: 21 additions & 0 deletions tests/psuutil_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import sys
import os
from unittest import mock

import pytest
from click.testing import CliRunner

test_path = os.path.dirname(os.path.abspath(__file__))
modules_path = os.path.dirname(test_path)
sys.path.insert(0, modules_path)

sys.modules['sonic_platform'] = mock.MagicMock()
import psuutil.main as psuutil


class TestPsuutil(object):

def test_version(self):
runner = CliRunner()
result = runner.invoke(psuutil.cli.commands['version'], [])
assert result.output.rstrip() == 'psuutil version {}'.format(psuutil.VERSION)

0 comments on commit 310d203

Please sign in to comment.