diff --git a/xCAT-openbmc-py/lib/python/agent/common/rest.py b/xCAT-openbmc-py/lib/python/agent/common/rest.py index 46a9f7b2e..2d0ec5c43 100644 --- a/xCAT-openbmc-py/lib/python/agent/common/rest.py +++ b/xCAT-openbmc-py/lib/python/agent/common/rest.py @@ -9,6 +9,7 @@ from gevent.subprocess import Popen, PIPE import requests import urllib3 urllib3.disable_warnings() +from requests.auth import AuthBase import exceptions as xcat_exception @@ -17,6 +18,7 @@ class RestSession(object): def __init__(self): self.session = requests.Session() self.cookies = None + self.auth = None def request(self, method, url, headers, data=None, timeout=30): @@ -24,6 +26,7 @@ class RestSession(object): response = self.session.request(method, url, data=data, headers=headers, + auth=self.auth, verify=False, timeout=timeout) except requests.exceptions.ConnectionError as e: @@ -60,6 +63,9 @@ class RestSession(object): if not self.cookies: self.cookies = requests.utils.dict_from_cookiejar(self.session.cookies) + if not self.auth and 'X-Auth-Token' in response.headers: + self.auth = XTokenAuth(response.headers['X-Auth-Token']) + return response def extract_server_and_port(self, message_string, format="STRING"): @@ -127,3 +133,13 @@ class RestSession(object): raise SelfServerException(error) return response + +class XTokenAuth(AuthBase): + + def __init__(self,authToken): + + self.authToken=authToken + + def __call__(self, auth): + auth.headers['X-Auth-Token']=self.authToken + return(auth) diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/executor/redfish_power.py b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/redfish_power.py new file mode 100644 index 000000000..75b6e079d --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/executor/redfish_power.py @@ -0,0 +1,34 @@ +#!/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 redfish_client as redfish + +import logging +logger = logging.getLogger('xcatagent') + +class RedfishPowerTask(ParallelNodesCommand): + """Executor for power-related actions.""" + + def get_state(self, **kw): + + node = kw['node'] + rf = redfish.RedfishRest(name=node, nodeinfo=kw['nodeinfo'], messager=self.callback, + debugmode=self.debugmode, verbose=self.verbose) + + state = 'Unknown' + try: + rf.login() + self.callback.info('%s: %s' % (node, state)) + except (SelfServerException, SelfClientException) as e: + self.callback.error(e.message, node) + + return state diff --git a/xCAT-openbmc-py/lib/python/agent/hwctl/redfish_client.py b/xCAT-openbmc-py/lib/python/agent/hwctl/redfish_client.py new file mode 100644 index 000000000..9e01def8f --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/hwctl/redfish_client.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# + +import os +import requests +import json +import time + +from common import utils, rest +from common.exceptions import SelfClientException, SelfServerException + +import logging +logger = logging.getLogger('xcatagent') + +HTTP_PROTOCOL = "https://" +PROJECT_URL = "/redfish/v1" + +SESSION_URL = PROJECT_URL + "/SessionService/Sessions" + +class RedfishRest(object): + + headers = {'Content-Type': 'application/json'} + + def __init__(self, name, **kwargs): + + self.name = name + self.username = None + self.password = None + + if 'nodeinfo' in kwargs: + for key, value in kwargs['nodeinfo'].items(): + setattr(self, key, value) + if not hasattr(self, 'bmcip'): + self.bmcip = self.name + + self.verbose = kwargs.get('debugmode') + self.messager = kwargs.get('messager') + + self.session = rest.RestSession() + self.root_url = HTTP_PROTOCOL + self.bmcip + + def _print_record_log (self, msg, cmd, error_flag=False): + + if self.verbose or error_flag: + localtime = time.asctime( time.localtime(time.time()) ) + log = self.name + ': [redfish_debug] ' + cmd + ' ' + msg + if self.verbose: + self.messager.info(localtime + ' ' + log) + logger.debug(log) + + def _print_error_log (self, msg, cmd): + + self._print_record_log(msg, cmd, True) + + def _log_request (self, method, url, headers, data=None, files=None, file_path=None, cmd=''): + + header_str = ' '.join([ "%s: %s" % (k, v) for k,v in headers.items() ]) + msg = 'curl -k -X %s -H \"%s\" ' % (method, header_str) + + if cmd != 'login': + msg += '-H \"X-Auth-Token: xxxxxx\" ' + + if data: + if cmd == 'login': + data = data.replace('"Password": "%s"' % self.password, '"Password": "xxxxxx"') + data = '-d \'%s\'' % data + msg += '%s %s -v' % (url, data) + else: + msg += url + + self._print_record_log(msg, cmd) + return msg + + def request (self, method, resource, headers=None, payload=None, timeout=30, cmd=''): + + httpheaders = headers or RedfishRest.headers + url = resource + if not url.startswith(HTTP_PROTOCOL): + url = self.root_url + resource + + data = None + if payload: + data=json.dumps(payload) + + self._log_request(method, url, httpheaders, data=data, cmd=cmd) + + try: + response = self.session.request(method, url, headers=httpheaders, data=data, timeout=timeout) + return self.handle_response(response, cmd=cmd) + except SelfServerException as e: + if cmd == 'login': + e.message = "Login to BMC failed: Can't connect to {0} {1}.".format(e.host_and_port, e.detail_msg) + else: + e.message = 'BMC did not respond. ' \ + 'Validate BMC configuration and retry the command.' + self._print_error_log(e.message, cmd) + raise + except ValueError: + error = 'Received wrong format response: %s' % response + self._print_error_log(error, cmd) + raise SelfServerException(error) + + def handle_response (self, resp, cmd=''): + + data = resp.json() + code = resp.status_code + + if code != requests.codes.ok and code != requests.codes.created: + + description = ''.join(data['error']['@Message.ExtendedInfo'][0]['Message']) + error = '[%d] %s' % (code, description) + self._print_error_log(error, cmd) + raise SelfClientException(error, code) + + if cmd == 'login' and not 'X-Auth-Token' in resp.headers: + raise SelfServerException('Login Failed: Did not get Session Token from response') + + self._print_record_log('%s %s' % (code, data['Name']), cmd) + return data + + def login(self): + + payload = { "UserName": self.username, "Password": self.password } + self.request('POST', SESSION_URL, payload=payload, timeout=20, cmd='login') + diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py index ca9521513..fd9a5bb6f 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/base.py @@ -2,7 +2,8 @@ from common import utils import gevent from gevent.pool import Pool -MODULE_MAP = {"openbmc": "OpenBMCManager"} +MODULE_MAP = {"openbmc": "OpenBMCManager", + "redfish": "RedfishManager"} class BaseManager(object): def __init__(self, messager, cwd): diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/redfish.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/redfish.py new file mode 100644 index 000000000..1e37e9493 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/redfish.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +############################################################################### +# IBM(c) 2018 EPL license http://www.eclipse.org/legal/epl-v10.html +############################################################################### +# -*- coding: utf-8 -*- +# + +import os +import gevent +import re +import sys +from docopt import docopt,DocoptExit + +from common import utils +from common import exceptions as xcat_exception +from hwctl.executor.redfish_power import RedfishPowerTask +from hwctl.power import DefaultPowerManager + +from xcatagent import base +import logging +logger = logging.getLogger('xcatagent') +try: + if not logger.handlers: + utils.enableSyslog('xcat.agent') +except: + pass + +DEBUGMODE = False +VERBOSE = False + +# global variables of rpower +POWER_REBOOT_OPTIONS = ('boot', 'reset') +POWER_SET_OPTIONS = ('on', 'off', 'bmcreboot', 'softoff') +POWER_GET_OPTIONS = ('bmcstate', 'state', 'stat', 'status') + +class RedfishManager(base.BaseManager): + def __init__(self, messager, cwd, nodes=None, envs=None): + super(RedfishManager, self).__init__(messager, cwd) + self.nodes = nodes + self.debugmode = (envs and envs.get('debugmode')) or None + #TODO, remove the global variable DEBUGMODE + global DEBUGMODE + DEBUGMODE = envs['debugmode'] + + if self.debugmode: + logger.setLevel(logging.DEBUG) + + def rpower(self, nodesinfo, args): + + # 1, parse args + rpower_usage = """ + Usage: + rpower [-V|--verbose] [boot|bmcreboot|bmcstate|off|on|reset|softoff|stat|state|status] + + Options: + -V --verbose rpower verbose mode. + """ + + try: + opts=docopt(rpower_usage, argv=args) + + self.verbose=opts.pop('--verbose') + action=[k for k,v in opts.items() if v][0] + except Exception as e: + # It will not be here as perl has validation for args + self.messager.error("Failed to parse arguments for rpower: %s" % args) + return + + # 2, validate the args + if action not in (POWER_GET_OPTIONS + POWER_SET_OPTIONS + POWER_REBOOT_OPTIONS): + self.messager.error("Not supported subcommand for rpower: %s" % action) + return + + # 3, run the subcommands + runner = RedfishPowerTask(nodesinfo, callback=self.messager, debugmode=self.debugmode, verbose=self.verbose) + DefaultPowerManager().get_power_state(runner) +