From fb48cdf0e26219de1f9b958db49c8afd02ca12da Mon Sep 17 00:00:00 2001 From: AndriiS Date: Tue, 28 Nov 2017 22:00:53 +0200 Subject: [PATCH] Added PSU CLI (#152) --- data/etc/bash_completion.d/psuutil | 8 ++ psuutil/__init__.py | 0 psuutil/main.py | 192 +++++++++++++++++++++++++++++ setup.py | 2 + show/main.py | 11 ++ sonic_psu/psu_base.py | 26 +++- 6 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 data/etc/bash_completion.d/psuutil create mode 100644 psuutil/__init__.py create mode 100644 psuutil/main.py diff --git a/data/etc/bash_completion.d/psuutil b/data/etc/bash_completion.d/psuutil new file mode 100644 index 0000000000..b70914f91b --- /dev/null +++ b/data/etc/bash_completion.d/psuutil @@ -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; diff --git a/psuutil/__init__.py b/psuutil/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/psuutil/main.py b/psuutil/main.py new file mode 100644 index 0000000000..fdef031c1a --- /dev/null +++ b/psuutil/main.py @@ -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() diff --git a/setup.py b/setup.py index 5a571ae032..2a52164879 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ def get_test_suite(): 'debug', 'pfcwd', 'sfputil', + 'psuutil', 'show', 'sonic_eeprom', 'sonic_installer', @@ -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', diff --git a/show/main.py b/show/main.py index 5a2399c833..3e735474d8 100755 --- a/show/main.py +++ b/show/main.py @@ -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") diff --git a/sonic_psu/psu_base.py b/sonic_psu/psu_base.py index fba50b4627..fe5c3974a6 100644 --- a/sonic_psu/psu_base.py +++ b/sonic_psu/psu_base.py @@ -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 + Retrieves the operational status of power supply unit (PSU) defined + by index 1-based - :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 + + :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 +