Skip to content

Commit 310d203

Browse files
authored
[psuutil] Refactor to utilize sonic-platform package (#1434)
#### 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
1 parent cf577cc commit 310d203

File tree

2 files changed

+105
-68
lines changed

2 files changed

+105
-68
lines changed

psuutil/main.py

Lines changed: 84 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,46 @@
55
# Command-line utility for interacting with PSU in SONiC
66
#
77

8-
try:
9-
import imp
10-
import os
11-
import sys
8+
import os
9+
import sys
1210

13-
import click
14-
from sonic_py_common import device_info, logger
15-
from tabulate import tabulate
16-
except ImportError as e:
17-
raise ImportError("%s - required module not found" % str(e))
11+
import click
12+
import sonic_platform
13+
from sonic_py_common import logger
14+
from tabulate import tabulate
1815

19-
VERSION = '1.0'
16+
17+
VERSION = '2.0'
2018

2119
SYSLOG_IDENTIFIER = "psuutil"
22-
PLATFORM_SPECIFIC_MODULE_NAME = "psuutil"
23-
PLATFORM_SPECIFIC_CLASS_NAME = "PsuUtil"
2420

25-
# Global platform-specific psuutil class instance
26-
platform_psuutil = None
21+
ERROR_PERMISSIONS = 1
22+
ERROR_CHASSIS_LOAD = 2
23+
ERROR_NOT_IMPLEMENTED = 3
2724

25+
# Global platform-specific Chassis class instance
26+
platform_chassis = None
2827

2928
# Global logger instance
3029
log = logger.Logger(SYSLOG_IDENTIFIER)
3130

3231

3332
# ==================== Methods for initialization ====================
3433

35-
# Loads platform specific psuutil module from source
36-
def load_platform_psuutil():
37-
global platform_psuutil
38-
39-
# Load platform module from source
40-
platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs()
34+
# Instantiate platform-specific Chassis class
35+
def load_platform_chassis():
36+
global platform_chassis
4137

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

49-
try:
50-
platform_psuutil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME)
51-
platform_psuutil = platform_psuutil_class()
52-
except AttributeError as e:
53-
log.log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)
54-
return -2
44+
if not platform_chassis:
45+
return False
5546

56-
return 0
47+
return True
5748

5849

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

6758
if os.geteuid() != 0:
6859
click.echo("Root privileges are required for this operation")
69-
sys.exit(1)
60+
sys.exit(ERROR_PERMISSIONS)
7061

71-
# Load platform-specific psuutil class
72-
err = load_platform_psuutil()
73-
if err != 0:
74-
sys.exit(2)
75-
76-
# 'version' subcommand
62+
# Load platform-specific Chassis class
63+
if not load_platform_chassis():
64+
sys.exit(ERROR_CHASSIS_LOAD)
7765

7866

67+
# 'version' subcommand
7968
@cli.command()
8069
def version():
8170
"""Display version info"""
8271
click.echo("psuutil version {0}".format(VERSION))
8372

84-
# 'numpsus' subcommand
85-
8673

74+
# 'numpsus' subcommand
8775
@cli.command()
8876
def numpsus():
8977
"""Display number of supported PSUs on device"""
90-
click.echo(str(platform_psuutil.get_num_psus()))
91-
92-
# 'status' subcommand
78+
num_psus = platform_chassis.get_num_psus()
79+
click.echo(str(num_psus))
9380

9481

82+
# 'status' subcommand
9583
@cli.command()
96-
@click.option('-i', '--index', default=-1, type=int, help="the index of PSU")
84+
@click.option('-i', '--index', default=-1, type=int, help='Index of the PSU')
9785
def status(index):
9886
"""Display PSU status"""
99-
supported_psu = list(range(1, platform_psuutil.get_num_psus() + 1))
100-
psu_ids = []
101-
if (index < 0):
102-
psu_ids = supported_psu
103-
else:
104-
psu_ids = [index]
105-
106-
header = ['PSU', 'Status']
87+
header = ['PSU', 'Model', 'Serial', 'Voltage (V)', 'Current (A)', 'Power (W)', 'Status', 'LED']
10788
status_table = []
10889

109-
for psu in psu_ids:
110-
msg = ""
111-
psu_name = "PSU {}".format(psu)
112-
if psu not in supported_psu:
113-
click.echo("Error! The {} is not available on the platform.\n"
114-
"Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus()))
115-
continue
116-
presence = platform_psuutil.get_psu_presence(psu)
117-
if presence:
118-
oper_status = platform_psuutil.get_psu_status(psu)
119-
msg = 'OK' if oper_status else "NOT OK"
120-
else:
121-
msg = 'NOT PRESENT'
122-
status_table.append([psu_name, msg])
90+
psu_list = platform_chassis.get_all_psus()
91+
92+
for psu in psu_list:
93+
psu_name = psu.get_name()
94+
status = 'NOT PRESENT'
95+
model = 'N/A'
96+
serial = 'N/A'
97+
voltage = 'N/A'
98+
current = 'N/A'
99+
power = 'N/A'
100+
led_color = 'N/A'
101+
102+
if psu.get_presence():
103+
try:
104+
status = 'OK' if psu.get_powergood_status() else 'NOT OK'
105+
except NotImplementedError:
106+
status = 'UNKNOWN'
107+
108+
try:
109+
model = psu.get_model()
110+
except NotImplementedError:
111+
pass
112+
113+
try:
114+
serial = psu.get_serial()
115+
except NotImplementedError:
116+
pass
117+
118+
try:
119+
voltage = psu.get_voltage()
120+
except NotImplementedError:
121+
pass
122+
123+
try:
124+
current = psu.get_current()
125+
except NotImplementedError:
126+
pass
127+
128+
try:
129+
power = psu.get_power()
130+
except NotImplementedError:
131+
pass
132+
133+
try:
134+
led_color = psu.get_status_led()
135+
except NotImplementedError:
136+
pass
137+
138+
status_table.append([psu_name, model, serial, voltage, current, power, status, led_color])
123139

124140
if status_table:
125-
click.echo(tabulate(status_table, header, tablefmt="simple"))
141+
click.echo(tabulate(status_table, header, tablefmt='simple', floatfmt='.2f'))
126142

127143

128144
if __name__ == '__main__':

tests/psuutil_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import sys
2+
import os
3+
from unittest import mock
4+
5+
import pytest
6+
from click.testing import CliRunner
7+
8+
test_path = os.path.dirname(os.path.abspath(__file__))
9+
modules_path = os.path.dirname(test_path)
10+
sys.path.insert(0, modules_path)
11+
12+
sys.modules['sonic_platform'] = mock.MagicMock()
13+
import psuutil.main as psuutil
14+
15+
16+
class TestPsuutil(object):
17+
18+
def test_version(self):
19+
runner = CliRunner()
20+
result = runner.invoke(psuutil.cli.commands['version'], [])
21+
assert result.output.rstrip() == 'psuutil version {}'.format(psuutil.VERSION)

0 commit comments

Comments
 (0)