diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/bmcconfig.py b/xCAT-openbmc-py/lib/python/agent/hwctl/bmcconfig.py new file mode 100644 index 000000000..5bdb5bc1d --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/bmcconfig.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# + +class BmcConfigInterface(object): + """Interface for bmc configuration related actions.""" + interface_type = 'bmcconfig' + version = '1.0' + + def dump_list(self, task): + return task.run('dump_list') + + def dump_generate(self, task): + return task.run("dump_generate") + + def dump_clear(self, task, id): + return task.run("dump_clear", id) + + def dump_download(self, task, id): + return task.run("dump_download", id) + + def dump_process(self, task): + return task.run("dump_process") + + def set_sshcfg(self, task): + return task.run("set_sshcfg") + + def set_ipdhcp(self, task): + return task.run("set_ipdhcp") + + def get_attributes(self, task, attributes): + return task.run("get_attributes", attributes) + + def set_attributes(self, task, attributes): + return task.run("set_attributes", attributes) + +class DefaultBmcConfigManager(BmcConfigInterface): + """Interface for BmcConfig actions.""" + pass diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_bmcconfig.py b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_bmcconfig.py new file mode 100644 index 000000000..b7ae94bed --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/openbmc_bmcconfig.py @@ -0,0 +1,132 @@ +#!/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') + +RSPCONFIG_GET_NETINFO=['ip', 'netmask', 'gateway', 'vlan', 'ipsrc', 'hostname'] +RSPCONFIG_SET_NETINFO=['ip', 'netmask', 'gateway', 'vlan'] + +class OpenBMCBmcConfigTask(ParallelNodesCommand): + + def dump_list(self, **kw): + return self.callback.info('dump_list') + + def dump_generate(self, **kw): + return self.callback.info("dump_generate") + + def dump_clear(self, id, **kw): + return self.callback.info("dump_clear id: %s" % id) + + def dump_download(self, id, **kw): + return self.callback.info("dump_download id: %s" % id) + + def dump_process(self, **kw): + return self.callback.info("dump_process: trigger, list and download") + + def set_sshcfg(self, **kw): + return self.callback.info("set_sshcfg") + + def set_ipdhcp(self, **kw): + return self.callback.info("set_ipdhcp") + + def get_attributes(self, attributes, **kw): + netinfo_dict={} + for attr in attributes: + if attr in RSPCONFIG_GET_NETINFO: + netinfo_dict[attr]=True + getnet=1 + elif openbmc.RSPCONFIG_APIS.has_key(attr): + self._get_apis_values(attr, **kw) + else: + self.callback.error("get_attributes can not deal with attr %s" % attr) + if len(netinfo_dict): + self._get_netinfo(netinfo_dict.has_key('ip'), netinfo_dict.has_key('ipsrc'), netinfo_dict.has_key('netmask'), + netinfo_dict.has_key('gateway'), netinfo_dict.has_key('vlan'), netinfo_dict.has_key('hostname'), **kw) + + def set_attributes(self, attributes, **kw): + netinfo_dict={'vlan':False} + for attr in attributes: + k,v = attr.split('=') + if k in RSPCONFIG_SET_NETINFO: + netinfo_dict[k] = v + elif k == 'hostname': + self._set_hostname(v, **kw) + elif openbmc.RSPCONFIG_APIS.has_key(k): + self._set_apis_values(k, v, **kw) + else: + return self.callback.error("set_attributes unsupported attribute:%s" % k) + if len(netinfo_dict) > 1 and (not netinfo_dict.has_key('ip') or not netinfo_dict.has_key('netmask') or not netinfo_dict.has_key('gateway')): + self.callback.info("set_attributes miss either ip, netmask or gateway to set network information") + else: + self._set_netinfo(netinfo_dict['ip'], netinfo_dict['netmask'], + netinfo_dict['gateway'], netinfo_dict['vlan']) + + def _set_hostname(self, hostname, **kw): + if hostname == '*': + if kw['nodeinfo']['bmc'] != kw['nodeinfo']['bmcip']: + hostname = kw['nodeinfo']['bmc'] + else: + hostname = '%s-bmc' % kw['node'] + return self.callback.info("set_hostname: %s" % hostname) + + def _set_apis_values(self, key, value, **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_apis_values(key, value) + except (SelfServerException, SelfClientException) as e: + self.callback.info("%s: %s" % (node, e.message)) + + self.callback.info("%s: BMC Setting %s..." % (node, openbmc.RSPCONFIG_APIS[key]['display_name'])) + + def _get_apis_values(self, key, **kw): + node = kw['node'] + obmc = openbmc.OpenBMCRest(name=node, nodeinfo=kw['nodeinfo'], messager=self.callback, + debugmode=self.debugmode, verbose=self.verbose) + try: + obmc.login() + value = obmc.get_apis_values(key) + + except (SelfServerException, SelfClientException) as e: + self.callback.info('%s: %s' % (node, e.message)) + + str_value = '0.'+str(value) + result = '%s: %s: %s' % (node, openbmc.RSPCONFIG_APIS[key]['display_name'], str_value.split('.')[-1]) + self.callback.info(result) + + def _set_netinfo(self, ip, netmask, gateway, vlan=False, **kw): + if vlan: + result = "set net(%s, %s, %s) for vlan %s" % (ip, netmask, gateway, vlan) + else: + result = "set net(%s, %s, %s) for eth0" % (ip, netmask, gateway) + return self.callback.info("set_netinfo %s" % result) + + def _get_netinfo(self, ip=False, ipsrc=False, netmask=False, gateway=False, vlan=False, hostname=False, **kw): + result = '' + if ip: + result += "Get IP, " + if netmask: + result += "Get Mask, " + if gateway: + result += "Get Gateway, " + if ipsrc: + result += "Get IP source, " + if hostname: + result += "Get BMC hostname, " + if vlan: + result += "Get BMC vlan." + return self.callback.info("get_netinfo: %s" % result) 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 94ba4ea60..f1fa55057 100644 --- a/xCAT-openbmc-py/lib/python/agent/hwctl/openbmc_client.py +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/openbmc_client.py @@ -141,6 +141,49 @@ FIRM_URLS = { } } +RSPCONFIG_APIS = { + 'autoreboot' : { + 'baseurl': "/control/host0/auto_reboot/", + 'set_url': "attr/AutoReboot", + 'get_url': "attr/AutoReboot", + 'display_name': "BMC AutoReboot", + }, + 'powersupplyredundancy':{ + 'baseurl': "/sensors/chassis/PowerSupplyRedundancy/", + 'set_url': "/action/setValue", + 'get_url': "/action/getValue", + 'get_method': 'POST', + 'get_data': '[]', + 'display_name': "BMC PowerSupplyRedundancy", + 'attr_values': { + 'disabled': "Disables", + 'enabled': "Enabled", + }, + }, + 'powerrestorepolicy': { + 'baseurl': "/control/host0/power_restore_policy/", + 'set_url': "attr/PowerRestorePolicy", + 'get_url': "attr/PowerRestorePolicy", + 'display_name': "BMC PowerRestorePolicy", + 'attr_values': { + 'restore': "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.Restore", + 'always_on': "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn", + 'always_off': "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOff", + }, + }, + 'bootmode': { + 'baseurl': "/control/host0/boot/", + 'set_url': "attr/BootMode", + 'get_url': "attr/BootMode", + 'display_name':"BMC BootMode", + 'attr_values': { + 'regular': "xyz.openbmc_project.Control.Boot.Mode.Modes.Regular", + 'safe': "xyz.openbmc_project.Control.Boot.Mode.Modes.Safe", + 'setup': "xyz.openbmc_project.Control.Boot.Mode.Modes.Setup", + }, + }, +} + RESULT_OK = 'ok' RESULT_FAIL = 'fail' @@ -443,6 +486,31 @@ class OpenBMCRest(object): return bool(func_list), fw_dict + def set_apis_values(self, key, value): + attr_info = RSPCONFIG_APIS[key] + if not attr_info.has_key('set_url'): + raise SelfServerException("config %s failed, not url available" % key) + set_url = attr_info['baseurl']+attr_info['set_url'] + if attr_info.has_key('attr_values') and attr_info['attr_values'].has_key(value): + data = attr_info['attr_values'][value] + else: + data = value + self.request('PUT', set_url, payload={"data": data}, cmd="set_%s" % key) + + def get_apis_values(self, key): + attr_info = RSPCONFIG_APIS[key] + if not attr_info.has_key('get_url'): + raise SelfServerException("Reading %s failed, not url available" % key) + get_url = attr_info['baseurl']+attr_info['get_url'] + + method = 'GET' + if attr_info.has_key('get_method'): + method = attr_info['get_method'] + data = None + if attr_info.has_key('get_data'): + data={"data": attr_info['get_data']} + return self.request(method, get_url, payload=data, cmd="get_%s" % key) + class OpenBMCImage(object): def __init__(self, rawid, data=None): self.id = rawid.split('/')[-1] diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py index 0591a217d..621e7301f 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py @@ -9,7 +9,8 @@ import os import time import sys import gevent -from docopt import docopt +import re +from docopt import docopt,DocoptExit from common import utils from common import exceptions as xcat_exception @@ -18,11 +19,13 @@ from hwctl.executor.openbmc_setboot import OpenBMCBootTask from hwctl.executor.openbmc_inventory import OpenBMCInventoryTask from hwctl.executor.openbmc_power import OpenBMCPowerTask from hwctl.executor.openbmc_sensor import OpenBMCSensorTask +from hwctl.executor.openbmc_bmcconfig import OpenBMCBmcConfigTask from hwctl.beacon import DefaultBeaconManager from hwctl.setboot import DefaultBootManager from hwctl.inventory import DefaultInventoryManager from hwctl.power import DefaultPowerManager from hwctl.sensor import DefaultSensorManager +from hwctl.bmcconfig import DefaultBmcConfigManager from xcatagent import base import openbmc_rest @@ -77,6 +80,46 @@ RFLASH_URLS = { } } +RSPCONFIG_GET_OPTIONS = ['ip','ipsrc','netmask','gateway','vlan','hostname','bootmode','autoreboot','powersupplyredundancy','powerrestorepolicy'] +RSPCONFIG_SET_OPTIONS = { + 'ip':'.*', + 'netmask':'.*', + 'gateway':'.*', + 'vlan':'\d+', + 'hostname':"\*|.*", + 'autoreboot':"^0|1$", + 'powersupplyredundancy':"^enabled$|^disabled$", + 'powerrestorepolicy':"^always_on$|^always_off$|^restore$", + 'bootmode':"^regular$|^safe$|^setup$", +} +RSPCONFIG_USAGE = """ +Handle rspconfig operations. + +Usage: + rspconfig -h|--help + rspconfig dump [[-l|--list] | [-g|--generate] | [-c|--clear ] | [-d|--download ]] [-V|--verbose] + rspconfig sshcfg [-V|--verbose] + rspconfig ip=dhcp [-V|--verbose] + rspconfig get [...] [-V|--verbose] + rspconfig set [...] [-V|--verbose] + +Options: + -V,--verbose Show verbose message + -l,--list List are dump files + -g,--generate Trigger a new dump file + -c,--clear The id of file to clear or all if specify 'all' + -d,--download The id of file to download or all if specify 'all' + +The supported attributes to get are: %s + +The supported attributes and its values to set are: + ip= netmask= gateway= [vlan=] + hostname=*| + autoreboot={0|1} + powersupplyredundancy={enabled|disabled} + powerrestorepolicy={always_on|always_off|restore} +""" % RSPCONFIG_GET_OPTIONS + XCAT_LOG_DIR = "/var/log/xcat" XCAT_LOG_RFLASH_DIR = XCAT_LOG_DIR + "/rflash/" @@ -663,7 +706,56 @@ class OpenBMCManager(base.BaseManager): DefaultPowerManager().reboot(runner, optype=action) else: DefaultPowerManager().set_power_state(runner, power_state=action) + def rspconfig(self, nodesinfo, args): + try: + opts=docopt(RSPCONFIG_USAGE, argv=args) + except DocoptExit as e: + self.messager.error("Failed to parse args by docopt: %s" % e) + return + except Exception as e: + self.messager.error("Failed to parse arguments for rspconfig: %s" % args) + return + self.verbose=opts.pop('--verbose') + runner = OpenBMCBmcConfigTask(nodesinfo, callback=self.messager, debugmode=self.debugmode, verbose=self.verbose) + if opts['dump']: + if opts['--list']: + DefaultBmcConfigManager().dump_list(runner) + elif opts['--generate']: + DefaultBmcConfigManager().dump_generate(runner) + elif opts['--clear']: + DefaultBmcConfigManager().dump_clear(runner, opts['--clear']) + elif opts['--download']: + DefaultBmcConfigManager().dump_download(runner, opts['--download']) + else: + DefaultBmcConfigManager().dump_process(runner) + elif opts['sshcfg']: + DefaultBmcConfigManager().set_sshcfg(runner) + elif opts['ip=dhcp']: + DefaultBmcConfigManager().set_ipdhcp(runner) + elif opts['get']: + unsupport_list=list(set(opts['']) - set(RSPCONFIG_GET_OPTIONS)) + if len(unsupport_list) > 0: + self.messager.error("Have unsupported option: %s" % unsupport_args) + return + else: + DefaultBmcConfigManager().get_attributes(runner, opts['']) + elif opts['set']: + rc=0 + for attr in opts['']: + k,v = attr.split('=') + if not RSPCONFIG_SET_OPTIONS.has_key(k): + self.messager.error("The attribute %s is not support to set" % k) + rc=1 + elif not re.match(RSPCONFIG_SET_OPTIONS[k], v): + self.messager.error("The value %s is invalid for %s" %(v, k)) + rc=1 + if rc: + return + else: + DefaultBmcConfigManager().set_attributes(runner, opts['']) + else: + self.messager.error("Failed to deal with rspconfig: %s" % args) def rsetboot(self, nodesinfo, args): # 1, parse args @@ -736,7 +828,6 @@ class OpenBMCManager(base.BaseManager): else: DefaultSensorManager().get_sensor_info(runner, action) - def _get_full_path(self,file_path): if type(self.cwd) == 'unicode': dir_path = self.cwd diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py index 86a6867de..f9b16bda4 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/server.py @@ -92,8 +92,12 @@ class Server(object): if not hasattr(manager, req['command']): messager.error("command %s is not supported" % req['command']) func = getattr(manager, req['command']) + # translate unicode string to normal string to avoid docopt error + new_args=[] + for a in req['args']: + new_args.append(a.encode('utf-8')) # call the function in the specified manager - func(req['nodeinfo'], req['args']) + func(req['nodeinfo'], new_args) # after the method returns, the request should be handled # completely, close the socket for client if not self.standalone: diff --git a/xCAT-server/lib/xcat/plugins/openbmc2.pm b/xCAT-server/lib/xcat/plugins/openbmc2.pm index 98be371f6..92ce69da9 100644 --- a/xCAT-server/lib/xcat/plugins/openbmc2.pm +++ b/xCAT-server/lib/xcat/plugins/openbmc2.pm @@ -17,6 +17,7 @@ use xCAT::Utils; use xCAT::Usage; use xCAT::SvrUtils; use xCAT::OPENBMC; +use xCAT_plugin::openbmc; #------------------------------------------------------- @@ -36,6 +37,7 @@ sub handled_commands { rpower => 'nodehm:mgt=openbmc', rsetboot => 'nodehm:mgt=openbmc', rvitals => 'nodehm:mgt=openbmc', + rspconfig => 'nodehm:mgt=openbmc', }; } @@ -114,6 +116,10 @@ sub process_request { $callback = shift; my $noderange = $request->{node}; my $check = parse_node_info($noderange); + if (&refactor_args($request)) { + xCAT::MsgUtils->message("E", { data => ["Failed to refactor arguments"] }, $callback); + return; + } $callback->({ errorcode => [$check] }) if ($check); return unless(%node_info); my $pid = xCAT::OPENBMC::start_python_agent(); @@ -231,6 +237,8 @@ sub parse_args { unless ($subcommand =~ /^all$|^altitude$|^fanspeed$|^leds$|^power$|^temp$|^voltage$|^wattage$/) { return ([ 1, "Unsupported command: $command $subcommand" ]); } + } elsif ($command eq 'rspconfig') { + xCAT_plugin::openbmc::parse_args('rspconfig', $extrargs, $noderange); } else { return ([ 1, "Unsupported command: $command" ]); } @@ -304,4 +312,31 @@ sub parse_node_info { return $rst; } +#------------------------------------------------------- + +=head3 refactor_args + + refractor args to be easily dealt by python client + +=cut + +#------------------------------------------------------- + +sub refactor_args { + my $request = shift; + my $command = $request->{command}->[0]; + my $extrargs = $request->{arg}; + if ($command eq "rspconfig") { + my $subcommand = $extrargs->[0]; + if ($subcommand !~ /^dump$|^sshcfg$|^ip=dhcp$/) { + if (grep /=/, @$extrargs) { + unshift @$extrargs, "set"; + } else { + unshift @$extrargs, "get"; + } + } + } + return 0; +} + 1;