diff --git a/pyghmi/ipmi/oem/lenovo/imm.py b/pyghmi/ipmi/oem/lenovo/imm.py index a889400d..583750b0 100644 --- a/pyghmi/ipmi/oem/lenovo/imm.py +++ b/pyghmi/ipmi/oem/lenovo/imm.py @@ -1784,7 +1784,96 @@ class XCCClient(IMMClient): progress({'phase': 'complete'}) self.weblogout() + def grab_redfish_response_emptyonerror(self, url, body=None, method=None): + rsp, status = self.grab_redfish_response_with_status(url, body, method) + if status >= 200 and status < 300: + return rsp + return {} + + def grab_redfish_response_with_status(self, url, body=None, method=None): + return self.wc.grab_json_response_with_status(url, body, headers={ + 'Authorization': 'Basic %s' % base64.b64encode( + (self.username + ':' + self.password).encode('utf8') + ).decode('utf8'), + 'Content-Type': 'application/json'}, method=method) + + def redfish_update_firmware(self, usd, filename, data, progress, bank): + if usd['HttpPushUriTargetsBusy']: + raise pygexc.TemporaryError('Cannot run multiple updates to same ' + 'target concurrently') + upurl = usd['HttpPushUri'] + self.grab_redfish_response_with_status( + '/redfish/v1/UpdateService', + {'HttpPushUriTargetsBusy': True}, method='PATCH') + try: + if bank == 'backup': + self.grab_redfish_response_with_status( + '/redfish/v1/UpdateService', + {'HttpPushUriTargets': + ['/redfish/v1/UpdateService' + '/FirmwareInventory/BMC-Backup']}, method='PATCH') + wc = self.wc.dupe() + wc.set_basic_credentials(self.username, self.password) + uploadthread = webclient.FileUploader(wc, upurl, filename, + data, formwrap=False, + excepterror=False) + uploadthread.start() + while uploadthread.isAlive(): + uploadthread.join(3) + if progress: + progress({'phase': 'upload', + 'progress': 100 * wc.get_upload_progress()}) + if uploadthread.rspstatus >= 300 or uploadthread.rspstatus < 200: + rsp = uploadthread.rsp + errmsg = '' + try: + rsp = json.loads(rsp) + errmsg = Exception( + rsp['error']['@Message.ExtendedInfo'][0]['Message']) + except Exception: + raise Exception(uploadthread.rsp) + raise Exception(errmsg) + rsp = json.loads(uploadthread.rsp) + monitorurl = rsp['TaskMonitor'] + complete = False + while not complete: + pgress, status = self.grab_redfish_response_with_status( + monitorurl) + if status < 200 or status >= 300: + raise Exception(pgress) + if not pgress: + break + for msg in pgress.get('Messages', []): + if 'Verify failed' in msg.get('Message', ''): + raise Exception(msg['Message']) + state = pgress['TaskState'] + if state in ('Cancelled', 'Exception', + 'Interrupted', 'Suspended'): + raise Exception(json.dumps(pgress['Messages'])) + pct = float(pgress['PercentComplete']) + complete = state == 'Completed' + progress({'phase': 'apply', 'progress': pct}) + if not complete: + ipmisession.Session.pause(3) + if bank == 'backup': + return 'complete' + return 'pending' + finally: + self.grab_redfish_response_with_status( + '/redfish/v1/UpdateService', + {'HttpPushUriTargetsBusy': False}, method='PATCH') + self.grab_redfish_response_with_status( + '/redfish/v1/UpdateService', + {'HttpPushUriTargets': []}, method='PATCH') + def update_firmware(self, filename, data=None, progress=None, bank=None): + usd = self.grab_redfish_response_emptyonerror( + '/redfish/v1/UpdateService') + rfishurl = usd.get('HttpPushUri', None) + if rfishurl: + self.weblogout() + return self.redfish_update_firmware( + usd, filename, data, progress, bank) result = None if self.updating: raise pygexc.TemporaryError('Cannot run multiple updates to same ' diff --git a/pyghmi/util/webclient.py b/pyghmi/util/webclient.py index 2629dc52..eafea2e5 100644 --- a/pyghmi/util/webclient.py +++ b/pyghmi/util/webclient.py @@ -48,7 +48,7 @@ uploadforms = {} class FileUploader(threading.Thread): def __init__(self, webclient, url, filename, data=None, formname=None, - otherfields=()): + otherfields=(), formwrap=True, excepterror=True): self.wc = webclient self.url = url self.filename = filename @@ -57,13 +57,17 @@ class FileUploader(threading.Thread): self.formname = formname self.rsp = '' self.rspstatus = 500 + self.formwrap = formwrap + self.excepterror = excepterror super(FileUploader, self).__init__() def run(self): try: self.rsp = self.wc.upload( self.url, self.filename, self.data, self.formname, - otherfields=self.otherfields) + otherfields=self.otherfields, formwrap=self.formwrap, + excepterror=self.excepterror) + self.rspstatus = self.wc.rspstatus except Exception: self.rspstatus = self.wc.rspstatus raise @@ -279,7 +283,7 @@ class SecureHTTPConnection(httplib.HTTPConnection, object): self._currdl.getheader('content-length')) def upload(self, url, filename, data=None, formname=None, - otherfields=()): + otherfields=(), formwrap=True, excepterror=True): """Upload a file to the url :param url: @@ -290,24 +294,34 @@ class SecureHTTPConnection(httplib.HTTPConnection, object): """ if data is None: data = open(filename, 'rb') - self._upbuffer = io.BytesIO(get_upload_form( - filename, data, formname, otherfields)) - ulheaders = self.stdheaders.copy() - ulheaders['Content-Type'] = b'multipart/form-data; boundary=' + BND - ulheaders['Content-Length'] = len(uploadforms[filename]) - self.ulsize = len(uploadforms[filename]) + ulhdrs = self.stdheaders.copy() + if formwrap: + self._upbuffer = io.BytesIO(get_upload_form( + filename, data, formname, otherfields)) + ulhdrs['Content-Type'] = b'multipart/form-data; boundary=' + BND + ulhdrs['Content-Length'] = len(uploadforms[filename]) + self.ulsize = len(uploadforms[filename]) + else: + curroff = data.tell() + data.seek(0, 2) + self.ulsize = data.tell() + data.seek(curroff, 0) + self._upbuffer = data + ulhdrs['Content-Type'] = b'application/octet-stream' + ulhdrs['Content-Length'] = self.ulsize webclient = self.dupe() - webclient.request('POST', url, self._upbuffer, ulheaders) + webclient.request('POST', url, self._upbuffer, ulhdrs) rsp = webclient.getresponse() # peer updates in progress should already have pointers, # subsequent transactions will cause memory to needlessly double, # but easiest way to keep memory relatively low - try: - del uploadforms[filename] - except KeyError: # something could have already deleted it - pass + if formwrap: + try: + del uploadforms[filename] + except KeyError: # something could have already deleted it + pass self.rspstatus = rsp.status - if rsp.status != 200: + if excepterror and (rsp.status < 200 or rsp.status >= 300): raise Exception('Unexpected response in file upload: %s' % rsp.read()) body = rsp.read()