diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py index 1276bc85b..0b301ec42 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc.py @@ -1,18 +1,63 @@ from xcatagent import base +import os import time import sys import gevent +import utils import xcat_exception -import rest +import openbmc_rest HTTP_PROTOCOL = "https://" PROJECT_URL = "/xyz/openbmc_project" RESULT_OK = 'ok' +RESULT_FAIL = 'fail' DEBUGMODE = False +VERBOSE = False +all_nodes_result = {} + +# global variables of rflash +RFLASH_OPTIONS = { + "-a" : "activate", + "--activate" : "activate", + "-c" : "check", + "--check" : "check", + "-d" : "direcory", + "--delete" : "delete", + "-l" : "list", + "--list" : "list", + "-u" : "upload", + "--upload" : "upload", +} + +RFLASH_URLS = { + "activate" : { + "url" : PROJECT_URL + "/software/#ACTIVATE_ID#/attr/RequestedActivation", + "field" : "xyz.openbmc_project.Software.Activation.RequestedActivations.Active", + }, + "delete" : { + "url" : PROJECT_URL + "/software/#DELETE_ID#/action/Delete", + "field" : [], + }, + "upload" : { + "url" : "/upload/image/", + }, + "priority" : { + "url" : PROJECT_URL + "/software/#PRIORITY_ID#/attr/Priority", + "field" : False, + } +} + +XCAT_LOG_DIR = "/var/log/xcat" +XCAT_LOG_RFLASH_DIR = XCAT_LOG_DIR + "/rflash/" + +# global variable of firmware information +FIRM_URL = PROJECT_URL + "/software/enumerate" + +# global variables of rpower POWER_SET_OPTIONS = ('on', 'off', 'bmcreboot', 'softoff') POWER_GET_OPTIONS = ('bmcstate', 'state', 'stat', 'status') @@ -68,21 +113,383 @@ class OpenBMC(base.BaseDriver): def __init__(self, messager, name, node_info): super(OpenBMC, self).__init__(messager) self.node = name - for key, value in node_info.items() : + for key, value in node_info.items(): setattr(self, key, value) global DEBUGMODE - self.client = rest.RestSession(messager, DEBUGMODE) + self.client = openbmc_rest.OpenBMCRest(name, messager, DEBUGMODE) - def _login(self) : + def _login(self): """ Login :raise: error message if failed """ url = HTTP_PROTOCOL + self.bmcip + '/login' data = { "data": [ self.username, self.password ] } - self.client.request('POST', url, OpenBMC.headers, data, self.node, 'login') + self.client.request('POST', url, OpenBMC.headers, data, 'login') return RESULT_OK - def _set_power_onoff(self, subcommand) : + def _msg_process_rflash (self, msg, update_dict, checkv): + """deal with msg during rflash + :param msg: the msg want to process + """ + if not checkv: + self.messager.info('%s: %s' % (self.node, msg)) + elif VERBOSE: + self.messager.info('%s: %s' % (self.node, msg)) + self.rflash_log_handle.writelines(msg + '\n') + self.rflash_log_handle.flush() + if update_dict: + utils.update2Ddict(update_dict, self.node, 'result', [msg]) + + def _firm_info(self, status): + """List firmware information including additional + called by rflash check and rinv firm + :returns: firmware information + """ + firm_output = [] + try: + (has_functional, firm_info) = self._get_firm_info(status) + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + firm_output.append(e.message) + return firm_output + + keys = firm_info.keys() + keys.sort() + for key in keys: + flag = '' + if 'is_functional' in firm_info[key]: + flag = '*' + elif ('Priority' in firm_info[key] and + firm_info[key]['Priority'] == '0'): + if not has_functional: + flag = '*' + else: + flag = '+' + + if not flag and not VERBOSE: + continue + + firm_output.append('%s Firmware Product: %s (%s)%s' % + (firm_info[key]['Purpose'], + firm_info[key]['Version'], + firm_info[key]['Activation'], flag)) + if 'ExtendedVersion' in firm_info[key]: + extendeds = firm_info[key]['ExtendedVersion'].split(',') + extendeds.sort() + for extended in extendeds: + firm_output.append('%s Firmware Product: ' \ + '-- additional info: %s' % \ + (firm_info[key]['Purpose'], extended)) + + return firm_output + + def _get_firm_info(self, status): + """get firmware information + :param status: current status + :returns: firmware version information + """ + firm_info = {} + has_functional = False + url = HTTP_PROTOCOL + self.bmcip + FIRM_URL + response = self.client.request('GET', url, OpenBMC.headers, '', status) + functional_url = PROJECT_URL + '/software/functional' + + for key in response['data']: + key_id = key.split('/')[-1] + if key_id == 'functional': + for endpoint in response['data'][key]['endpoints']: + purpose = response['data'][endpoint]['Purpose'].split('.')[-1] + key_sort = purpose + '-' + endpoint.split('/')[-1] + + utils.update2Ddict(firm_info, key_sort, 'is_functional', True) + has_functional = True + + if 'Version' in response['data'][key]: + purpose = response['data'][key]['Purpose'].split('.')[-1] + key_sort = purpose + '-' + key_id + if (functional_url in response['data'] and + key in response['data'][functional_url]['endpoints']): + utils.update2Ddict(firm_info, key_sort, 'is_functional', True) + utils.update2Ddict(firm_info, key_sort, 'Version', + response['data'][key]['Version']) + utils.update2Ddict(firm_info, key_sort, 'Purpose', purpose) + utils.update2Ddict(firm_info, key_sort, 'Activation', + response['data'][key]['Activation'].split('.')[-1]) + if 'Priority' in response['data'][key]: + utils.update2Ddict(firm_info, key_sort, 'Priority', + str(response['data'][key]['Priority'])) + if 'ExtendedVersion' in response['data'][key]: + utils.update2Ddict(firm_info, key_sort, 'ExtendedVersion', + response['data'][key]['ExtendedVersion']) + if 'Progress' in response['data'][key]: + utils.update2Ddict(firm_info, key_sort, 'Progress', + response['data'][key]['Progress']) + + return (has_functional, firm_info) + + def _get_firm_id(self, firm_list): + """get firmware id + :param firm_list: the list of firmware versions + :return: result and info list + """ + firm_ids = [] + url = HTTP_PROTOCOL + self.bmcip + FIRM_URL + + for i in range(6): + try: + response = self.client.request('GET', url, OpenBMC.headers, + '', 'rflash_check_id') + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + self._msg_process_rflash(e.message, all_nodes_result, False) + return (RESULT_FAIL, []) + + for key in response['data']: + if 'Version' in response['data'][key]: + if response['data'][key]['Version'] in firm_list: + firm_id = key.split('/')[-1] + upload_msg = 'Firmware upload successful. ' \ + 'Attempting to activate firmware: ' \ + '%s (ID: %s)' % \ + (response['data'][key]['Version'], firm_id) + self._msg_process_rflash(upload_msg, {}, False) + firm_ids.append(firm_id) + firm_list.remove(response['data'][key]['Version']) + + if firm_list: + for firm_ver in firm_list: + retry_msg = 'Could not find ID for firmware %s to '\ + 'activate, waiting %d seconds and retry...' \ + % (firm_ver, 10) + self._msg_process_rflash(upload_msg, {}, True) + gevent.sleep( 10 ) + else: + break + + if firm_list: + for firm_ver in firm_list: + error = 'Could not find firmware %s after waiting %d seconds.' \ + % (firm_ver, 10*6) + self._msg_process_rflash(upload_msg, {}, False) + error_list.append(error) + utils.update2Ddict(all_nodes_result, self.node, 'result', error_list) + return (RESULT_FAIL, []) + + return (RESULT_OK, firm_ids) + + def _check_id_status(self, firm_id_list): + """check firm id status + :param firm_id_list: list of firm ids want to check + :return: result + """ + result = RESULT_OK + set_priority_ids = [] + process_status = {} + failed_msg = [] + for i in range(80): + try: + (has_functional, firm_info) = self._get_firm_info('rflash_check_status') + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + self._msg_process_rflash(e.message, all_nodes_result, False) + return (RESULT_FAIL, set_priority_ids) + + activation_num = 0 + for key in firm_info: + firm_id = key.split('-')[-1] + if firm_id in firm_id_list: + activation_state = firm_info[key]['Activation'] + firm_version = firm_info[key]['Version'] + if activation_state == 'Failed': + activation_msg = 'Firmware %s activation failed.' % (firm_version) + self._msg_process_rflash(activation_msg, {}, False) + failed_msg.append(activation_msg) + result = RESULT_FAIL + firm_id_list.rempove(firm_id) + if activation_state == 'Active': + activation_msg = 'Firmware %s activation successful.' % (firm_version) + self._msg_process_rflash(activation_msg, {}, False) + firm_id_list.remove(firm_id) + priority = firm_info[key]['Priority'] + if priority != '0': + set_priority_ids.append(firm_id) + if activation_state == 'Activating': + activating_progress_msg = 'Activating %s ... %s%%' \ + % (firm_version, firm_info[key]['Progress']) + self._msg_process_rflash(activating_progress_msg, {}, True) + process_status[firm_id] = activating_progress_msg + + if not firm_id_list: + break + gevent.sleep( 15 ) + + if firm_id_list: + result = RESULT_FAIL + for firm_id in firm_id_list: + if firm_id in process_status: + failed_msg.append('After %d seconds check the current status is %s' \ + % (80*15, process_status[firm_id])) + + if failed_msg: + utils.update2Ddict(all_nodes_result, self.node, 'result', [failed_msg]) + + return (result, set_priority_ids) + + def _set_priority(self, priority_ids): + """set firmware priority to 0 + :param priority_ids: list of firmware ids + :return ok if success + :return error msg if failed + """ + for priority_id in priority_ids: + url = (HTTP_PROTOCOL + self.bmcip + + RFLASH_URLS['priority']['url'].replace('#PRIORITY_ID#', priority_id)) + data = { "data": RFLASH_URLS['priority']['field'] } + try: + response = self.client.request('PUT', url, OpenBMC.headers, + data, 'rflash_set_priority') + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + return e.message + + return RESULT_OK + + def _rflash_activate_id(self, activate_id): + """rflash activate id + :param activate_id: the id want to activate + :raise: error message if failed + """ + url = (HTTP_PROTOCOL + self.bmcip + + RFLASH_URLS['activate']['url'].replace('#ACTIVATE_ID#', activate_id)) + data = { "data": RFLASH_URLS['activate']['field'] } + try: + response = self.client.request('PUT', url, OpenBMC.headers, + data, 'rflash_activate') + except xcat_exception.SelfServerException as e: + return e.message + except xcat_exception.SelfClientException as e: + code = e.code + if code == 403: + return 'Error: Invalid ID provided to activate. ' \ + 'Use the -l option to view valid firmware IDs.' + return e.message + + return RESULT_OK + + def _rflash_activate(self, activate_arg): + """ACTIVATE firmware + called by rflash activate + :param activate_arg: firmware tar ball or firmware id + :return: ok if success + :raise: error message if failed + """ + activate_id = activate_version = '' + if 'activate_id' in activate_arg: + activate_id = activate_arg['activate_id'] + if 'update_file' in activate_arg: + result = self._rflash_upload(activate_arg['update_file']) + if result != RESULT_OK: + self._msg_process_rflash(result, all_nodes_result, False) + return + + activate_version = activate_arg['activate_version'] + (result, info) = self._get_firm_id([activate_version]) + if result == RESULT_OK: + activate_id = info.pop(0) + else: + return + + result = self._rflash_activate_id(activate_id) + if result != RESULT_OK: + self._msg_process_rflash(result, all_nodes_result, False) + return + else: + flash_started_msg = 'rflash %s started, please wait...' % activate_version + self._msg_process_rflash(flash_started_msg, {}, False) + + firm_id_list = [activate_id] + (result, priority_ids) = self._check_id_status(firm_id_list) + if result == RESULT_OK: + utils.update2Ddict(all_nodes_result, self.node, 'result', 'OK') + if priority_ids: + self._set_priority(priority_ids) + + def _rflash_delete(self, delete_id): + """Delete firmware on OpenBMC + called by rflash delete + :param delete_id: firmware id want to delete + :returns: ok if success + :raise: error message if failed + """ + url = (HTTP_PROTOCOL + self.bmcip + + RFLASH_URLS['delete']['url'].replace('#DELETE_ID#', delete_id)) + data = { "data": RFLASH_URLS['delete']['field'] } + try: + response = self.client.request('POST', url, OpenBMC.headers, + data, 'rflash_delete') + except xcat_exception.SelfServerException as e: + return e.message + except xcat_exception.SelfClientException as e: + code = e.code + if code == 404: + return 'Error: Invalid ID provided to delete. ' \ + 'Use the -l option to view valid firmware IDs.' + return e.message + + return RESULT_OK + + + def _rflash_list(self): + """List firmware information + called by rflash list + :returns: firmware version if success + :raise: error message if failed + """ + firm_output = [] + try: + (has_functional, firm_info) = self._get_firm_info('rflash_list') + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + firm_output.append(e.message) + return firm_output + + firm_output.append('%-8s %-7s %-10s %-s' % ('ID', 'Purpose', 'State', 'Version')) + firm_output.append('-' * 55) + + for key in firm_info: + status = firm_info[key]['Activation'] + if 'is_functional' in firm_info[key]: + status += '(*)' + elif 'Priority' in firm_info[key] and firm_info[key]['Priority'] == '0': + if not has_functional: + status += '(*)' + else: + status += '(+)' + + firm_output.append('%-8s %-7s %-10s %-s' % (key.split('-')[-1], + firm_info[key]['Purpose'], status, firm_info[key]['Version'])) + + return firm_output + + def _rflash_upload(self, upload_file): + """ Upload *.tar file to OpenBMC server + :param upload_file: file to upload + """ + url = HTTP_PROTOCOL + self.bmcip + RFLASH_URLS['upload']['url'] + headers = {'Content-Type': 'application/octet-stream'} + uploading_msg = 'Uploading %s ...' % upload_file + self._msg_process_rflash(uploading_msg, {}, True) + try: + self.client.request_upload('PUT', url, headers, + upload_file, 'rflash_upload') + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + result = e.message + return result + + return RESULT_OK + + def _set_power_onoff(self, subcommand): """ Set power on/off/softoff/bmcreboot :param subcommand: subcommand for rpower :returns: ok if success @@ -90,17 +497,19 @@ class OpenBMC(base.BaseDriver): """ url = HTTP_PROTOCOL + self.bmcip + RPOWER_URLS[subcommand]['url'] data = { "data": RPOWER_URLS[subcommand]['field'] } - try : - response = self.client.request('PUT', url, OpenBMC.headers, data, self.node, 'rpower_' + subcommand) + try: + response = self.client.request('PUT', url, OpenBMC.headers, + data, 'rpower_' + subcommand) except (xcat_exception.SelfServerException, - xcat_exception.SelfClientException) as e : + xcat_exception.SelfClientException) as e: if subcommand != 'bmcreboot': result = e.message return result return RESULT_OK - def _get_power_state(self, subcommand) : + + def _get_power_state(self, subcommand): """ Get power current state :param subcommand: state/stat/status/bmcstate :returns: current state if success @@ -109,61 +518,62 @@ class OpenBMC(base.BaseDriver): result = '' bmc_not_ready = 'NotReady' url = HTTP_PROTOCOL + self.bmcip + RPOWER_URLS['state']['url'] - try : - response = self.client.request('GET', url, OpenBMC.headers, '', self.node, 'rpower_' + subcommand) - except xcat_exception.SelfServerException, e : + try: + response = self.client.request('GET', url, OpenBMC.headers, + '', 'rpower_' + subcommand) + except xcat_exception.SelfServerException, e: if subcommand == 'bmcstate': result = bmc_not_ready - else : + else: result = e.message - except xcat_exception.SelfClientException, e : + except xcat_exception.SelfClientException, e: result = e.message - if result : + if result: return result - for key in response['data'] : + for key in response['data']: key_type = key.split('/')[-1] - if key_type == 'bmc0' : + if key_type == 'bmc0': bmc_current_state = response['data'][key]['CurrentBMCState'].split('.')[-1] - if key_type == 'chassis0' : + if key_type == 'chassis0': chassis_current_state = response['data'][key]['CurrentPowerState'].split('.')[-1] - if key_type == 'host0' : + if key_type == 'host0': host_current_state = response['data'][key]['CurrentHostState'].split('.')[-1] - if subcommand == 'bmcstate' : - if bmc_current_state == 'Ready' : + if subcommand == 'bmcstate': + if bmc_current_state == 'Ready': return bmc_current_state - else : + else: return bmc_not_ready - if chassis_current_state == 'Off' : + if chassis_current_state == 'Off': return chassis_current_state - elif chassis_current_state == 'On' : - if host_current_state == 'Off' : + elif chassis_current_state == 'On': + if host_current_state == 'Off': return 'chassison' - elif host_current_state == 'Quiesced' : + elif host_current_state == 'Quiesced': return host_current_state - elif host_current_state == 'Running' : + elif host_current_state == 'Running': return host_current_state - else : + else: return 'Unexpected chassis state=' + host_current_state - else : + else: return 'Unexpected chassis state=' + chassis_current_state - def _rpower_boot(self) : + def _rpower_boot(self): """Power boot :returns: 'reset' if success :raise: error message if failed """ result = self._set_power_onoff('off') - if result != RESULT_OK : + if result != RESULT_OK: return result self.messager.update_node_attributes('status', self.node, POWER_STATE_DB['off']) start_timeStamp = int(time.time()) - for i in range (0,30) : + for i in range (0,30): status = self._get_power_state('state') if status in RPOWER_STATE and RPOWER_STATE[status] == 'off': break @@ -173,64 +583,128 @@ class OpenBMC(base.BaseDriver): if status not in RPOWER_STATE or RPOWER_STATE[status] != 'off': wait_time = str(end_timeStamp - start_timeStamp) - result = 'Error: Sent power-off command but state did not change to off after waiting ' + wait_time + ' seconds. (State=' + status + ').' + result = 'Error: Sent power-off command but state did not change ' \ + 'to off after waiting %s seconds. (State= %s).' % (wait_time, status) return result result = self._set_power_onoff('on') return result + def rflash(self, args): + """handle rflash command + :param args: subcommands and parameters for rflash + """ + subcommand = args[0] + if subcommand == 'activate' or subcommand == 'upload': + self.rflash_log_file = XCAT_LOG_RFLASH_DIR + '/' + self.node + '.log' + self.rflash_log_handle = open(self.rflash_log_file, 'a') - def rpower(self, args) : + try: + result = self._login() + except (xcat_exception.SelfServerException, + xcat_exception.SelfClientException) as e: + result = e.message + + if result != RESULT_OK: + self.messager.info('%s: %s'% (self.node,result)) + if subcommand == 'activate' or subcommand == 'upload': + self.rflash_log_handle.writelines(result + '\n') + self.rflash_log_handle.flush() + if subcommand == 'activate': + utils.update2Ddict(all_nodes_result, self.node, 'result', [result]) + return + + if subcommand == 'activate': + activate_arg = args[1] + self._rflash_activate(activate_arg) + + if subcommand == 'check': + firm_info = self._firm_info('rflash_check') + for i in firm_info: + result = '%s: %s' % (self.node, i) + self.messager.info(result) + + if subcommand == 'delete': + firmware_id = args[1] + result = self._rflash_delete(firmware_id) + if result == RESULT_OK: + result = '%s: [%s] Firmware removed' % (self.node, firmware_id) + self.messager.info(result) + else: + result = '%s: %s' % (self.node, result) + self.messager.info(result) + + if subcommand == 'list': + firm_info = self._rflash_list() + for i in firm_info: + result = '%s: %s' % (self.node, i) + self.messager.info(result) + + if subcommand == 'upload': + upload_file = args[1] + result = self._rflash_upload(upload_file) + if result == RESULT_OK: + result = 'Firmware upload successful. Use -l option to list.' + self._msg_process_rflash(result, {}, False) + else: + self._msg_process_rflash(result, {}, False) + + if subcommand == 'activate' or subcommand == 'upload': + self.rflash_log_handle.close() + + + def rpower(self, args): """handle rpower command :param args: subcommands for rpower """ subcommand = args[0] - try : + try: result = self._login() - except xcat_exception.SelfServerException as e : - if subcommand == 'bmcstate' : + except xcat_exception.SelfServerException as e: + if subcommand == 'bmcstate': result = '%s: %s' % (self.node, RPOWER_STATE['NotReady']) - else : + else: result = '%s: %s' % (self.node, e.message) - except xcat_exception.SelfClientException as e : + except xcat_exception.SelfClientException as e: result = '%s: %s' % (self.node, e.message) - if result != RESULT_OK : + if result != RESULT_OK: self.messager.info(result) - self._update2Ddict(node_rst, self.node, 'rst', result) return + new_status = '' - if subcommand in POWER_SET_OPTIONS : + if subcommand in POWER_SET_OPTIONS: result = self._set_power_onoff(subcommand) - if result == RESULT_OK : + if result == RESULT_OK: result = RPOWER_STATE[subcommand] new_status = POWER_STATE_DB.get(subcommand, '') - if subcommand in POWER_GET_OPTIONS : + if subcommand in POWER_GET_OPTIONS: tmp_result = self._get_power_state(subcommand) result = RPOWER_STATE.get(tmp_result, tmp_result) - if subcommand == 'boot' : + if subcommand == 'boot': result = self._rpower_boot() - if result == RESULT_OK : + if result == RESULT_OK: result = RPOWER_STATE[subcommand] new_status = POWER_STATE_DB.get(subcommand, '') - if subcommand == 'reset' : + if subcommand == 'reset': status = self._get_power_state('state') if status == 'Off' or status == 'chassison': result = RPOWER_STATE['Off'] - else : + else: result = self._rpower_boot() - if result == RESULT_OK : + if result == RESULT_OK: result = RPOWER_STATE[subcommand] new_status = POWER_STATE_DB.get(subcommand, '') message = '%s: %s' % (self.node, result) self.messager.info(message) - if new_status : + if new_status: self.messager.update_node_attributes('status', self.node, new_status) + class OpenBMCManager(base.BaseManager): def __init__(self, messager, cwd, nodes, envs): super(OpenBMCManager, self).__init__(messager, cwd) @@ -238,5 +712,101 @@ class OpenBMCManager(base.BaseManager): global DEBUGMODE DEBUGMODE = envs['debugmode'] + def _get_full_path(self,file_path): + if type(self.cwd) == 'unicode': + dir_path = self.cwd + else: + dir_path = self.cwd[0] + return '%s/%s' % (dir_path,file_path) + + def _check_verbose(self, args): + verbose_list = ('-V', '--verbose') + for i in verbose_list: + if i in args: + global VERBOSE + VERBOSE = True + args.remove(i) + + def _summary(self, nodes_num, title): + if all_nodes_result: + success_num = failed_num = 0 + failed_list = [] + for key in all_nodes_result: + if all_nodes_result[key]['result'] == 'OK': + success_num += 1 + else: + failed_num += 1 + for errors in all_nodes_result[key]['result']: + for error in errors: + failed_list.append('%s: %s' % (key, error)) + self.messager.info('-' * 55) + self.messager.info('%s complete: Total=%d Success=%d Failed=%d' % \ + (title, nodes_num, success_num, failed_num)) + if failed_list: + for i in failed_list: + self.messager.info(i) + self.messager.info('-' * 55) + + def rflash(self, nodeinfo, args): + if not os.path.exists(XCAT_LOG_RFLASH_DIR): + os.makedirs(XCAT_LOG_RFLASH_DIR) + nodes_num = len(self.nodes) + self._check_verbose(args) + + for key,value in RFLASH_OPTIONS.items(): + if key in args: + args.remove(key) + args.insert(0, value) + break + + upload_file = None + activate_arg = {} + args_num = len(args) + subcommand = args[0] + if (subcommand == 'upload' or subcommand == 'activate' or + (subcommand == 'check' and args_num > 1)): + arg_type = args[1].split('.')[-1] + if arg_type == 'tar': + upload_file = args[1] + if not os.path.isabs(upload_file): + upload_file = self._get_full_path(upload_file) + + if (not os.access(upload_file, os.F_OK) or + not os.access(upload_file, os.R_OK)): + error = 'Error: Cannot access %s. Check the management ' \ + 'node and/or service nodes.' % upload_file + self.messager.error(error) + return + activate_arg['update_file'] = upload_file + else: + activate_arg['activate_id'] = args[1] + + if (subcommand == 'check' or subcommand == 'activate') and upload_file: + grep_cmd = '/usr/bin/grep -a' + version_cmd = grep_cmd + ' ^version= ' + upload_file + purpose_cmd = grep_cmd + ' purpose= ' + upload_file + firmware_ver = os.popen(version_cmd).readlines()[0].split('=')[-1].strip() + purpose_ver = os.popen(purpose_cmd).readlines()[0].split('=')[-1].strip() + if subcommand == 'check': + self.messager.info('TAR %s Firmware Product Version: %s' \ + % (purpose_ver,firmware_ver)) + else: + activate_arg['activate_version'] = firmware_ver + activate_arg['purpose'] = purpose_ver.split('.')[-1] + + if subcommand == 'activate': + args[1] = activate_arg + + if subcommand == 'upload': + args[1] = upload_file + + if subcommand == 'upload' or subcommand == 'activate' and upload_file: + self.messager.info('Attempting to upload %s, please wait...' % upload_file) + + super(OpenBMCManager, self).process_nodes_worker('openbmc', 'OpenBMC', + self.nodes, nodeinfo, 'rflash', args) + self._summary(nodes_num, 'Firmware update') + def rpower(self, nodeinfo, args): - super(OpenBMCManager, self).process_nodes_worker('openbmc', 'OpenBMC', self.nodes, nodeinfo, 'rpower', args) + super(OpenBMCManager, self).process_nodes_worker('openbmc', 'OpenBMC', + self.nodes, nodeinfo, 'rpower', args) diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc_rest.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc_rest.py new file mode 100644 index 000000000..40152a118 --- /dev/null +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/openbmc_rest.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +import requests +import json +import time + +import rest +import xcat_exception + +class OpenBMCRest: + + def __init__(self, name, messager, debugmode): + self.session = rest.RestSession() + self.name = name + self.messager = messager + self.debugmode = debugmode + + def _print_record_log (self, log_string, status): + if self.debugmode : + localtime = time.asctime( time.localtime(time.time()) ) + log = self.name + ': [openbmc_debug] ' + status + ' ' + log_string + self.messager.info(localtime + ' ' + log) + self.messager.syslog(log) + + def _request_log (self, method, url, headers, data, files): + log_string = 'curl -k -c cjar -b cjar' + log_string += ' -X %s' % method + for key,value in headers.items(): + header_data = key + ": " + value + log_string += ' -H "' + header_data + '"' + log_string += ' %s' % url + + if data: + log_string += ' -d \'%s\'' % data + if files: + log_string += ' -T \'%s\'' % files + + return log_string + + + def _response_check (self, response, response_dict, status): + if response.status_code != requests.codes.ok: + description = ''.join(response_dict['data']['description']) + error = 'Error: [%d] %s' % (response.status_code, description) + self._print_record_log(error, status) + code = response.status_code + raise xcat_exception.SelfClientException(error, code) + else: + self._print_record_log(response_dict['message'], status) + + def request (self, method, url, headers, in_data, status): + data = log_data = '' + + if in_data: + data = json.dumps(in_data) + log_data = data + if status == 'login': + in_data['data'][1] = 'xxxxxx' + log_data = json.dumps(in_data) + + log_string = self._request_log(method, url, headers, log_data, '') + self._print_record_log(log_string, status) + + try: + response = self.session.request(method, url, headers, data) + except xcat_exception.SelfServerException as e: + self._print_record_log(e.message, status) + raise xcat_exception.SelfServerException(e.message) + + try: + response_dict = response.json() + except ValueError: + error = 'Error: Received wrong format response: %s' % response + self._print_record_log(error, status) + raise xcat_exception.SelfServerException(error) + + self._response_check(response, response_dict, status) + + return response_dict + + + def request_upload (self, method, url, headers, files, status): + for key,value in headers.items(): + header_data = key + ': ' + value + request_cmd_log = 'curl -k -c cjar -b cjar -H "%s" -X %s -T %s %s -s' \ + % (header_data, method, files, url) + log_string = self._request_log(method, url, headers, '', files) + self._print_record_log(log_string, status) + + response = self.session.request_upload(method, url, header_data, files) + + if not response: + error = 'Error: Did not receive response from OpenBMC after ' \ + 'running command form \'%s\'' % request_cmd_log + raise xcat_exception.SelfServerException(error) + + try: + response_dict = json.loads(response) + except ValueError: + error = 'Error: Received wrong format response: %s: %s' % \ + (request_cmd_log, response) + self._print_record_log(error, status) + raise xcat_exception.SelfServerException(error) + + if response_dict['message'] != '200 OK': + error = 'Error: Failed to upload update file %s : %s-%s' % \ + (files, response_dict['message'], \ + ''.join(response_dict['data']['description'])) + self._print_record_log(error, status) + raise xcat_exception.SelfClientException(error, code) + + self._print_record_log(response_dict['message'], status) + + return diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py index c3057ddb3..35cbb449d 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/rest.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import requests -import json -import time +from gevent.subprocess import Popen, PIPE import urllib3 urllib3.disable_warnings() @@ -9,76 +8,34 @@ import xcat_exception class RestSession : - def __init__(self, messager, debugmode) : + def __init__(self): self.session = requests.Session() - self.messager = messager - self.debugmode = debugmode + self.cookies = None - def _print_record_log (self, node, log_string, status) : - if self.debugmode : - localtime = time.asctime( time.localtime(time.time()) ) - log = node + ': [openbmc_debug] ' + status + ' ' + log_string - self.messager.info(localtime + ' ' + log) - self.messager.syslog(log) - - def _request_log (self, method, url, headers, data): - log_string = 'curl -k -c cjar' - log_string += ' -X %s' % method - for key,value in headers.items() : - header_data = key + ": " + value - log_string += ' -H "' + header_data + '"' - log_string += ' %s' % url - - if data : - log_string += ' -d \'%s\'' % data - - return log_string - - def request (self, method, url, headers, in_data, node, status) : - if in_data : - data = json.dumps(in_data) - else : - data = '' - - if status == 'login' : - in_data['data'][1] = 'xxxxxx' - log_data = json.dumps(in_data) - else : - log_data = data - - log_string = self._request_log(method, url, headers, log_data) - self._print_record_log(node, log_string, status) - - response = '' - error = '' - try : + def request (self, method, url, headers, data): + try: response = self.session.request(method, url, - data=data, - headers=headers, - verify=False, - timeout=30) - except requests.exceptions.ConnectionError : - error = 'Error: BMC did not respond. Validate BMC configuration and retry the command.' - except requests.exceptions.Timeout : - error = 'Error: Timeout to connect to server' + data=data, + headers=headers, + verify=False, + timeout=30) + except requests.exceptions.ConnectionError: + raise xcat_exception.SelfServerException( + 'Error: BMC did not respond. ' \ + 'Validate BMC configuration and retry the command.') + except requests.exceptions.Timeout: + raise xcat_exception.SelfServerException('Error: Timeout to connect to server') - if error : - self._print_record_log(node, error, status) - raise xcat_exception.SelfServerException(error) + if not self.cookies: + self.cookies = requests.utils.dict_from_cookiejar(self.session.cookies) - try : - response_dict = response.json() - except ValueError : - error = 'Error: Received wrong format response:' + response_dict - self._print_record_log(node, error, status) - raise xcat_exception.SelfServerException(error) + return response - if response.status_code != requests.codes.ok : - description = ''.join(response_dict['data']['description']) - error = 'Error: [%d] %s' % (response.status_code, description) - self._print_record_log(node, error, status) - raise xcat_exception.SelfClientException(error) - else : - self._print_record_log(node, response_dict['message'], status) - - return response_dict + def request_upload (self, method, url, headers, files): + request_cmd = 'curl -k -b sid=%s -H "%s" -X %s -T %s %s -s' % \ + (self.cookies['sid'], headers, method, files, url) + + sub = Popen(request_cmd, stdout=PIPE, shell=True) + response, err = sub.communicate() + + return response diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py index 8fff2403a..183f1ebb8 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/utils.py @@ -39,3 +39,11 @@ def recv_all(sock, size): buf_size += len(buf_part) buf = ''.join(buf_parts) return buf + + +def update2Ddict(updata_dict, key_a, key_b, value): + if key_a in updata_dict: + updata_dict[key_a].update({key_b: value}) + else: + updata_dict.update({key_a: {key_b: value}}) + diff --git a/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py b/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py index f544cf2c8..15af285c4 100644 --- a/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py +++ b/xCAT-openbmc-py/lib/python/agent/xcatagent/xcat_exception.py @@ -4,4 +4,6 @@ class SelfServerException(Exception) : pass class SelfClientException(Exception) : - pass + def __init__(self, message, code) : + super(Exception, self).__init__(message) + self.code = code diff --git a/xCAT-server/lib/xcat/plugins/openbmc2.pm b/xCAT-server/lib/xcat/plugins/openbmc2.pm index 3c59bbe0b..766791242 100644 --- a/xCAT-server/lib/xcat/plugins/openbmc2.pm +++ b/xCAT-server/lib/xcat/plugins/openbmc2.pm @@ -30,6 +30,7 @@ use xCAT::OPENBMC; sub handled_commands { return { + rflash => 'nodehm:mgt=openbmc', rpower => 'nodehm:mgt=openbmc', }; } @@ -136,13 +137,61 @@ sub parse_args { my $noderange = shift; my $subcommand = undef; - if (scalar(@ARGV) != 1 and ($command =~ /rpower/)) { + if (scalar(@ARGV) >= 2 and ($command =~ /rpower/)) { return ([ 1, "Only one option is supported at the same time for $command" ]); + } elsif (scalar(@ARGV) == 0 and $command =~ /rpower|rflash/) { + return ([ 1, "No option specified for $command" ]); } else { $subcommand = $ARGV[0]; } - if ($command eq "rpower") { + if ($command eq "rflash") { + my $verbose; + my ($activate, $check, $delete, $directory, $list, $upload) = (0) x 6; + my $no_host_reboot; + GetOptions( + 'a|activate' => \$activate, + 'c|check' => \$check, + 'delete' => \$delete, + 'd' => \$directory, + 'l|list' => \$list, + 'u|upload' => \$upload, + 'V|verbose' => \$verbose, + 'no-host-reboot' => \$no_host_reboot, + ); + my $option_num = $activate+$check+$delete+$directory+$list+$upload; + if ($option_num >= 2) { + return ([ 1, "Multiple options are not supported."]); + } elsif ($option_num == 0) { + 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); + if ($check) { + return ([ 1, "Invalid firmware specified with '-c|--check'."]) if (@ARGV and ($ARGV[0] !~ /.*\.tar$/i or $#ARGV >= 1)); + } + if ($activate or $delete or $upload) { + my $option = "-a|--activate"; + if ($upload) { + $option = "-u|--upload"; + } elsif ($delete) { + $option = "--delete" + } + return ([ 1, "Invalid firmware specified with '$option'"]) if (!@ARGV); + my $param = $ARGV[0]; + return ([ 1, "Invalid firmware specified with '$option': $param"]) if (($delete and $param !~ /^[[:xdigit:]]+$/i) + or ($activate and $param !~ /^[[:xdigit:]]+$/i and $param !~ /.*\.tar$/i) or ($upload and $param !~ /.*\.tar$/i)); + } + } + if ($directory) { + return ([ 1, "Unsupported command: $command '-d'" ]); + return ([ 1, "More than one directory specified is not supported."]) if ($#ARGV >= 1); + return ([ 1, "Invalid option specified with '-d'."]) if (!@ARGV); + } + if ($list) { + return ([ 1, "Invalid option specified with '-l|--list'."]) if (@ARGV); + } + } elsif ($command eq "rpower") { unless ($subcommand =~ /^on$|^off$|^softoff$|^reset$|^boot$|^bmcreboot$|^bmcstate$|^status$|^stat$|^state$/) { return ([ 1, "Unsupported command: $command $subcommand" ]); }