Skip to content

Commit

Permalink
Added PSU CLI (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndriiS authored and jleveque committed Nov 28, 2017
1 parent 0762204 commit fb48cdf
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 6 deletions.
8 changes: 8 additions & 0 deletions data/etc/bash_completion.d/psuutil
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
_psuutil_completion() {
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \
COMP_CWORD=$COMP_CWORD \
_PSUUTIL_COMPLETE=complete $1 ) )
return 0
}

complete -F _psuutil_completion -o default psuutil;
Empty file added psuutil/__init__.py
Empty file.
192 changes: 192 additions & 0 deletions psuutil/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env python
#
# main.py
#
# Command-line utility for interacting with PSU in SONiC
#

try:
import sys
import os
import subprocess
import click
import imp
import syslog
import types
import traceback
from tabulate import tabulate
except ImportError as e:
raise ImportError("%s - required module not found" % str(e))

VERSION = '1.0'

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

PLATFORM_ROOT_PATH = '/usr/share/sonic/device'
PLATFORM_ROOT_PATH_DOCKER = '/usr/share/sonic/platform'
SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen'
MINIGRAPH_PATH = '/etc/sonic/minigraph.xml'
HWSKU_KEY = "DEVICE_METADATA['localhost']['hwsku']"
PLATFORM_KEY = 'platform'

# Global platform-specific psuutil class instance
platform_psuutil = None


# ========================== Syslog wrappers ==========================


def log_info(msg, also_print_to_console=False):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_INFO, msg)
syslog.closelog()

if also_print_to_console:
print msg


def log_warning(msg, also_print_to_console=False):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_WARNING, msg)
syslog.closelog()

if also_print_to_console:
print msg


def log_error(msg, also_print_to_console=False):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_ERR, msg)
syslog.closelog()

if also_print_to_console:
print msg


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

# Returns platform and HW SKU
def get_platform_and_hwsku():
try:
proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-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, '-m', MINIGRAPH_PATH, '-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:
log_error("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:
log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)
return -2

return 0


# ==================== CLI commands and groups ====================


# This is our main entrypoint - the main 'psuutil' command
@click.group()
def cli():
"""psuutil - Command line utility for providing PSU status"""

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

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

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

# 'numpsus' subcommand
@cli.command()
def numpsus():
"Display the number of supported PSU in the device"
print(str(platform_psuutil.get_num_psus()))

# 'status' subcommand
@cli.command()
@click.option('-i', '--index', default=-1, type=int, help="the index of PSU")
@click.option('--textonly', is_flag=True, help="show the PSU status in a simple text format instead of table")
def status(index, textonly):
"""Display PSU status"""
supported_psu = range(1, platform_psuutil.get_num_psus() + 1)
psu_ids = []
if (index < 0):
psu_ids = supported_psu
else:
psu_ids = [index]

header = ['PSU', 'Status']
status_table = []

for psu in psu_ids:
msg = ""
psu_name = "PSU {}".format(psu)
if psu not in supported_psu:
status_table.append([psu_name, "NOT SUPPORTED"])
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])

if textonly:
for status in status_table:
print status[0] + ':' + status[1]
else:
print tabulate(status_table, header, tablefmt="grid")

if __name__ == '__main__':
cli()
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def get_test_suite():
'debug',
'pfcwd',
'sfputil',
'psuutil',
'show',
'sonic_eeprom',
'sonic_installer',
Expand Down Expand Up @@ -61,6 +62,7 @@ def get_test_suite():
'debug = debug.main:cli',
'pfcwd = pfcwd.main:cli',
'sfputil = sfputil.main:cli',
'psuutil = psuutil.main:cli',
'show = show.main:cli',
'sonic-clear = clear.main:cli',
'sonic_installer = sonic_installer.main:cli',
Expand Down
11 changes: 11 additions & 0 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,17 @@ def syseeprom():
"""Show system EEPROM information"""
run_command("sudo decode-syseeprom")

# 'psustatus' subcommand ("show platform psustatus")
@platform.command()
@click.option('-i', '--index', default=-1, type=int, help="the index of PSU")
def psustatus(index):
"""Show PSU status information"""
command = "sudo psuutil status"

if index >= 0:
command += " -i {}".format(index)

run_command(command)

#
# 'logging' command ("show logging")
Expand Down
26 changes: 20 additions & 6 deletions sonic_psu/psu_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,33 @@ class PsuBase(object):
@abc.abstractmethod
def get_num_psus(self):
"""
Retrieves the number of PSUs available on the device
Retrieves the number of PSUs supported on the device
:return: An integer, the number of PSUs available on the device
:return: An integer, the number of PSUs supported on the device
"""
return 0

@abc.abstractmethod
def get_psu_status(self, index):
"""
Retrieves the oprational status of power supply unit (PSU) defined
by index <index>
Retrieves the operational status of power supply unit (PSU) defined
by index 1-based <index>
:param index: An integer, index of the PSU of which to query status
:return: Boolean, True if PSU is operating properly, False if PSU is faulty
:param index: An integer, 1-based index of the PSU of which to query status
:return: Boolean,
- True if PSU is operating properly: PSU is inserted and powered in the device
- False if PSU is faulty: PSU is inserted in the device but not powered
"""
return False

@abc.abstractmethod
def get_psu_presence(self, index):
"""
Retrieves the presence status of power supply unit (PSU) defined
by 1-based index <index>
:param index: An integer, 1-based index of the PSU of which to query status
:return: Boolean, True if PSU is plugged, False if not
"""
return False

0 comments on commit fb48cdf

Please sign in to comment.