diff --git a/xCAT-openbmc-py/lib/python/agent/common/utils.py b/xCAT-openbmc-py/lib/python/agent/common/utils.py index 69f29efcc..9c2c2786f 100644 --- a/xCAT-openbmc-py/lib/python/agent/common/utils.py +++ b/xCAT-openbmc-py/lib/python/agent/common/utils.py @@ -7,6 +7,7 @@ import struct import sys import inspect +import re import logging from logging.handlers import SysLogHandler @@ -70,6 +71,17 @@ def update2Ddict(updata_dict, key_a, key_b, value): else: updata_dict.update({key_a: {key_b: value}}) +def emb_numbers(string): + re_digits = re.compile(r'(\d+)') + pieces = re_digits.split(string) + pieces[1::2] = map(int,pieces[1::2]) + return pieces + +def sort_string_with_numbers(origin_list): + new_list = [(emb_numbers(string),string) for string in origin_list] + new_list.sort() + return [string for __,string in new_list] + class Messager(object): def __init__(self, name=None): self.logger = logging.getLogger(name or 'xcatagent') diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/beacon.py b/xCAT-openbmc-py/lib/python/agent/hwctl/beacon.py new file mode 100644 index 000000000..0c4cb6672 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/beacon.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# + +class BeaconInterface(object): + """Interface for beacon-related actions.""" + interface_type = 'beacon' + version = '1.0' + + def set_beacon_state(self, task, beacon_state, timeout=None): + """Set the beacon state of the task's nodes. + + :param task: a Task instance containing the nodes to act on. + :param beacon_state: on|off beacon state. + :param timeout: timeout (in seconds) positive integer (> 0) for any + beacon state. ``None`` indicates to use default timeout. + """ + return task.run('set_state', beacon_state, timeout=timeout) + + +class DefaultBeaconManager(BeaconInterface): + """Interface for beacon-related actions.""" + pass diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_beacon.py b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_beacon.py new file mode 100644 index 000000000..e68f56f7a --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_beacon.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# +from __future__ import print_function +import gevent +import time + +from common.task import ParallelNodesCommand +from common.exceptions import SelfClientException, SelfServerException +from hwctl import openbmc_client as openbmc + +import logging +logger = logging.getLogger('xcatagent') + + +class OpenBMCBeaconTask(ParallelNodesCommand): + """Executor for beacon-related actions.""" + + def set_state(self, state, **kw): + + node = kw['node'] + obmc = openbmc.OpenBMCRest(name=node, nodeinfo=kw['nodeinfo'], messager=self.callback, + debugmode=self.debugmode, verbose=self.verbose) + try: + obmc.login() + obmc.set_beacon_state(state) + result = '%s: %s' % (node, state) + + except (SelfServerException, SelfClientException) as e: + result = '%s: %s' % (node, e.message) + + self.callback.info(result) diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_sensor.py b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_sensor.py new file mode 100644 index 000000000..0cc8a7af1 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_sensor.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# + +from __future__ import print_function +import gevent +import time + +from common.task import ParallelNodesCommand +from common.exceptions import SelfClientException, SelfServerException +from hwctl import openbmc_client as openbmc +from common import utils + +import logging +logger = logging.getLogger('xcatagent') + + +SENSOR_TYPE_UNIT = { + "altitude" : "Meters", + "fanspeed" : "RPMS", + "temp" : "DegreesC", + "voltage" : "Volts", + "wattage" : "Watts", +} + +SENSOR_POWER_UNITS = ("Amperes", "Joules", "Watts") + + +class OpenBMCSensorTask(ParallelNodesCommand): + """Executor for sensor-related actions.""" + + def _get_beacon_info(self, beacon_dict): + + info_list = [] + info_list.append('Front . . . . . : Power:%s Fault:%s Identify:%s' % + (beacon_dict.get('front_power', 'N/A'), + beacon_dict.get('front_fault', 'N/A'), + beacon_dict.get('front_id', 'N/A'))) + if (beacon_dict.get('fan0', 'N/A') == 'Off' and beacon_dict.get('fan1', 'N/A') == 'Off' and + beacon_dict.get('fan2', 'N/A') == 'Off' and beacon_dict.get('fan3', 'N/A') == 'Off'): + info_list.append('Front Fans . . : No LEDs On') + else: + info_list.append('Front Fans . . : fan0:%s fan1:%s fan2:%s fan3:%s' % + (beacon_dict.get('fan0', 'N/A'), beacon_dict.get('fan1', 'N/A'), + beacon_dict.get('fan2', 'N/A'), beacon_dict.get('fan3', 'N/A'))) + info_list.append('Rear . . . . . : Power:%s Fault:%s Identify:%s' % + (beacon_dict.get('rear_power', 'N/A'), + beacon_dict.get('rear_fault', 'N/A'), + beacon_dict.get('rear_id', 'N/A'))) + return info_list + + + def get_sensor_info(self, sensor_type, **kw): + + node = kw['node'] + obmc = openbmc.OpenBMCRest(name=node, nodeinfo=kw['nodeinfo'], messager=self.callback, + debugmode=self.debugmode, verbose=self.verbose) + + sensor_info = [] + try: + obmc.login() + sensor_info_dict = obmc.get_sensor_info() + + if sensor_type == 'all' or not sensor_type: + for sensor_key in sensor_info_dict: + sensor_info += sensor_info_dict[sensor_key] + sensor_info = utils.sort_string_with_numbers(sensor_info) + beacon_dict = obmc.get_beacon_info() + sensor_info += self._get_beacon_info(beacon_dict) + elif sensor_type == 'power': + for sensor_key in sensor_info_dict: + if sensor_key in SENSOR_POWER_UNITS: + sensor_info += sensor_info_dict[sensor_key] + sensor_info = utils.sort_string_with_numbers(sensor_info) + else: + sensor_unit = SENSOR_TYPE_UNIT[sensor_type] + if sensor_unit in sensor_info_dict: + sensor_info += sensor_info_dict[sensor_unit] + sensor_info = utils.sort_string_with_numbers(sensor_info) + + if not sensor_info: + sensor_info = ['No attributes returned from the BMC.'] + + for info in sensor_info: + self.callback.info( '%s: %s' % (node, info)) + + except (SelfServerException, SelfClientException) as e: + self.callback.info('%s: %s' % (node, e.message)) + + return sensor_info + + def get_beacon_info(self, **kw): + + node = kw['node'] + obmc = openbmc.OpenBMCRest(name=node, nodeinfo=kw['nodeinfo'], messager=self.callback, + debugmode=self.debugmode, verbose=self.verbose) + + beacon_info = [] + try: + obmc.login() + beacon_dict = obmc.get_beacon_info() + beacon_info = self._get_beacon_info(beacon_dict) + + if not beacon_info: + beacon_info = ['No attributes returned from the BMC.'] + + for info in beacon_info: + self.callback.info( '%s: %s' % (node, info)) + + except (SelfServerException, SelfClientException) as e: + self.callback.info('%s: %s' % (node, e.message)) + + return beacon_info + + diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/openbmc_client.py b/xCAT-openbmc-py/lib/python/agent/hwctl/openbmc_client.py index 2211c3981..caa8c3cda 100644 --- a/xCAT-openbmc-py/lib/python/agent/hwctl/openbmc_client.py +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/openbmc_client.py @@ -19,6 +19,34 @@ HTTP_PROTOCOL = "https://" PROJECT_URL = "/xyz/openbmc_project" PROJECT_PAYLOAD = "xyz.openbmc_project." +RBEACON_URLS = { + "path" : "/led/groups/enclosure_identify/attr/Asserted", + "on" : { + "field" : True, + }, + "off" : { + "field" : False, + }, +} + +LEDS_URL = "/led/physical/enumerate" + +LEDS_KEY_LIST = ("fan0", "fan1", "fan2", "fan3", + "front_id", "front_fault", "front_power", + "rear_id", "rear_fault", "rear_power") + +SENSOR_URL = "/sensors/enumerate" + +SENSOR_UNITS = { + "Amperes" : "Amps", + "DegreesC" : "C", + "Joules" : "Joules", + "Meters" : "Meters", + "RPMS" : "RPMS", + "Volts" : "Volts", + "Watts" : "Watts", +} + RPOWER_STATES = { "on" : "on", "off" : "off", @@ -308,6 +336,52 @@ class OpenBMCRest(object): error = 'Error: Received wrong format response: %s' % states raise SelfServerException(error) + def get_beacon_info(self): + + beacon_data = self.request('GET', LEDS_URL, cmd='get_beacon_info') + try: + beacon_dict = {} + for key, value in beacon_data.items(): + key_id = key.split('/')[-1] + if key_id in LEDS_KEY_LIST: + beacon_dict[key_id] = value['State'].split('.')[-1] + return beacon_dict + except KeyError: + error = 'Error: Received wrong format response: %s' % beacon_data + raise SelfServerException(error) + + def set_beacon_state(self, state): + + payload = { "data": RBEACON_URLS[state]['field'] } + self.request('PUT', RBEACON_URLS['path'], payload=payload, cmd='set_beacon_state') + + def get_sensor_info(self): + + sensor_data = self.request('GET', SENSOR_URL, cmd='get_sensor_info') + try: + sensor_dict = {} + for k, v in sensor_data.items(): + if 'Unit' in v: + unit = v['Unit'].split('.')[-1] + if unit in SENSOR_UNITS: + label = k.split('/')[-1].replace('_', ' ').title() + value = v['Value'] + scale = v['Scale'] + value = value * pow(10, scale) + value = '{:g}'.format(value) + if unit not in sensor_dict: + sensor_dict[unit] = [] + sensor_dict[unit].append('%s: %s %s' % (label, value, SENSOR_UNITS[unit])) + elif 'units' in v and 'value' in v: + label = k.split('/')[-1] + value = v['value'] + sensor_dict[label] = ['%s: %s' % (label, value)] + + return sensor_dict + except KeyError: + error = 'Error: Received wrong format response: %s' % sensor_data + raise SelfServerException(error) + def list_firmware(self): data = self.request('GET', FIRM_URLS['list']['path'], cmd='list_firmware') @@ -347,3 +421,4 @@ class OpenBMCImage(object): def __str__(self): return '%s-%s' % (self.purpose, self.id) + diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/sensor.py b/xCAT-openbmc-py/lib/python/agent/hwctl/sensor.py new file mode 100644 index 000000000..ad8727311 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/sensor.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# + +class SensorInterface(object): + """Interface for sensor-related actions.""" + interface_type = 'sensor' + version = '1.0' + + def get_sensor_info(self, task, sensor_type=None): + """Return the sensor info of the task's nodes. + + :param sensor_type: type of sensor info want to get. + :param task: a Task instance containing the nodes to act on. + :return: sensor info list + """ + return task.run('get_sensor_info', sensor_type) + + def get_beacon_info(self, task): + """Return the beacon info of the task's nodes. + + :param task: a Task instance containing the nodes to act on. + :return: beacon info list + """ + return task.run('get_beacon_info') + +class DefaultSensorManager(SensorInterface): + """Interface for sensor-related actions.""" + pass diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py index d11f76d51..82ec20b8f 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py @@ -13,10 +13,14 @@ from docopt import docopt from common import utils from common import exceptions as xcat_exception +from hwctl.executor.openbmc_beacon import OpenBMCBeaconTask from hwctl.executor.openbmc_power import OpenBMCPowerTask from hwctl.executor.openbmc_setboot import OpenBMCBootTask +from hwctl.executor.openbmc_sensor import OpenBMCSensorTask +from hwctl.beacon import DefaultBeaconManager from hwctl.power import DefaultPowerManager from hwctl.setboot import DefaultBootManager +from hwctl.sensor import DefaultSensorManager from xcatagent import base import openbmc_rest @@ -36,6 +40,9 @@ VERBOSE = False all_nodes_result = {} +# global variables of rbeacon +BEACON_SET_OPTIONS = ('on', 'off') + # global variables of rflash RFLASH_OPTIONS = { "-a" : "activate", @@ -83,6 +90,10 @@ POWER_GET_OPTIONS = ('bmcstate', 'state', 'stat', 'status') SETBOOT_GET_OPTIONS = ('stat', '') SETBOOT_SET_OPTIONS = ('cd', 'def', 'default', 'hd', 'net') +# global variables of rvitals +VITALS_OPTIONS = ('all', 'altitude', 'fanspeed', 'leds', 'power', + 'temp', 'voltage', 'wattage') + class OpenBMC(base.BaseDriver): headers = {'Content-Type': 'application/json'} @@ -541,6 +552,39 @@ class OpenBMCManager(base.BaseManager): if self.debugmode: logger.setLevel(logging.DEBUG) + def rbeacon(self, nodesinfo, args): + + # 1, parse args + rbeacon_usage = """ + Usage: + rbeacon [-V|--verbose] [on|off] + + Options: + -V --verbose rbeacon verbose mode. + """ + + try: + opts = docopt(rbeacon_usage, argv=args) + + self.verbose = opts.pop('--verbose') + action = [k for k,v in opts.items() if v][0] + except Exception as e: + self.messager.error("Failed to parse arguments for rbeacon: %s" % args) + return + + # 2, validate the args + if action is None: + self.messager.error("Not specify the subcommand for rbeacon") + return + + if action not in BEACON_SET_OPTIONS: + self.messager.error("Not supported subcommand for rbeacon: %s" % action) + return + + # 3, run the subcommands + runner = OpenBMCBeaconTask(nodesinfo, callback=self.messager, debugmode=self.debugmode, verbose=self.verbose) + DefaultBeaconManager().set_beacon_state(runner, beacon_state=action) + def rpower(self, nodesinfo, args): # 1, parse args @@ -617,6 +661,41 @@ class OpenBMCManager(base.BaseManager): else: DefaultBootManager().set_boot_state(runner, setboot_state=action, persistant=action_type) + def rvitals(self, nodesinfo, args): + + # 1, parse agrs + if not args: + args = ['all'] + + rvitals_usage = """ + Usage: + rvitals [-V|--verbose] [all|altitude|fanspeed|leds|power|temp|voltage|wattage] + + Options: + -V --verbose rvitals verbose mode. + """ + + try: + opts = docopt(rvitals_usage, argv=args) + + self.verbose = opts.pop('--verbose') + action = [k for k,v in opts.items() if v][0] + except Exception as e: + self.messager.error("Failed to parse arguments for rvitals: %s" % args) + return + + # 2, validate the args + if action not in VITALS_OPTIONS: + self.messager.error("Not supported subcommand for rvitals: %s" % action) + return + + # 3, run the subcommands + runner = OpenBMCSensorTask(nodesinfo, callback=self.messager, debugmode=self.debugmode, verbose=self.verbose) + if action == 'leds': + DefaultSensorManager().get_beacon_info(runner) + else: + DefaultSensorManager().get_sensor_info(runner, action) + def _get_full_path(self,file_path): if type(self.cwd) == 'unicode': diff --git a/xCAT-server/lib/xcat/plugins/openbmc2.pm b/xCAT-server/lib/xcat/plugins/openbmc2.pm index bc3161e2d..584e801e9 100644 --- a/xCAT-server/lib/xcat/plugins/openbmc2.pm +++ b/xCAT-server/lib/xcat/plugins/openbmc2.pm @@ -30,8 +30,11 @@ use xCAT::OPENBMC; sub handled_commands { return { + rbeacon => 'nodehm:mgt=openbmc', rflash => 'nodehm:mgt=openbmc', rpower => 'nodehm:mgt=openbmc', + rsetboot => 'nodehm:mgt=openbmc', + rvitals => 'nodehm:mgt=openbmc', }; } @@ -144,15 +147,19 @@ sub parse_args { return ([ 1, "Error parsing arguments." ]); } - if (scalar(@ARGV) >= 2 and ($command =~ /rpower/)) { + if (scalar(@ARGV) >= 2 and ($command =~ /rbeacon|rpower|rvitals/)) { return ([ 1, "Only one option is supported at the same time for $command" ]); - } elsif (scalar(@ARGV) == 0 and $command =~ /rpower|rflash/) { + } elsif (scalar(@ARGV) == 0 and $command =~ /rbeacon|rpower|rflash/) { return ([ 1, "No option specified for $command" ]); } else { $subcommand = $ARGV[0]; } - if ($command eq "rflash") { + if ($command eq "rbeacon") { + unless ($subcommand =~ /^on$|^off$/) { + return ([ 1, "Only 'on' or 'off' is supported for OpenBMC managed nodes."]); + } + } elsif ($command eq "rflash") { my ($activate, $check, $delete, $directory, $list, $upload) = (0) x 6; my $no_host_reboot; GetOptions( @@ -173,7 +180,7 @@ sub parse_args { return ([ 1, "Unsupported command: $command $arg" ]); } } - return ([ 1, "No options specified."]); + return ([ 1, "No options specified." ]); } if ($activate or $check or $delete or $upload) { return ([ 1, "More than one firmware specified is not supported."]) if ($#ARGV >= 1); @@ -213,6 +220,11 @@ sub parse_args { unless ($subcommand =~ /^net$|^hd$|^cd$|^def$|^default$|^stat$/) { return ([ 1, "Unsupported command: $command $subcommand" ]); } + } elsif ($command eq "rvitals") { + $subcommand = "all" if (!defined($ARGV[0])); + unless ($subcommand =~ /^all$|^altitude$|^fanspeed$|^leds$|^power$|^temp$|^voltage$|^wattage$/) { + return ([ 1, "Unsupported command: $command $subcommand" ]); + } } else { return ([ 1, "Unsupported command: $command" ]); }