From a9d888277342caec0f3b87589e2de8f97d9fca1c Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 10 Jun 2019 14:06:36 -0400 Subject: [PATCH] Implement upload_media for Lenovo Implement the Lenovo OEM mechanism for media upload under redfish command object. Change-Id: I47f7b8ba5751741a1c6fb322366b18a9120f7db3 --- pyghmi/ipmi/command.py | 2 +- pyghmi/redfish/command.py | 13 +++++++ pyghmi/redfish/oem/generic.py | 4 +++ pyghmi/redfish/oem/lenovo/xcc.py | 58 +++++++++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 944a1820..880047af 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -1982,7 +1982,7 @@ class Command(object): :param filename: The filename to use, the basename of the parameter will be given to the bmc. - :param filename: Optional callback for progress updates + :param progress: Optional callback for progress updates """ self.oem_init() return self._oem.upload_media(filename, progress) diff --git a/pyghmi/redfish/command.py b/pyghmi/redfish/command.py index 22f503b2..1480aa9d 100644 --- a/pyghmi/redfish/command.py +++ b/pyghmi/redfish/command.py @@ -1198,6 +1198,19 @@ class Command(object): """ return self.oem.apply_storage_configuration(cfgspec) + def upload_media(self, filename, progress=None): + """Upload a file to be hosted on the target BMC + + This will upload the specified data to + the BMC so that it will make it available to the system as an emulated + USB device. + + :param filename: The filename to use, the basename of the parameter + will be given to the bmc. + :param progress: Optional callback for progress updates + """ + return self.oem.upload_media(filename, progress) + if __name__ == '__main__': import os import sys diff --git a/pyghmi/redfish/oem/generic.py b/pyghmi/redfish/oem/generic.py index 265f7d31..db716f0c 100644 --- a/pyghmi/redfish/oem/generic.py +++ b/pyghmi/redfish/oem/generic.py @@ -52,6 +52,10 @@ class OEMHandler(object): raise exc.UnsupportedFunctionality( 'Remote storage configuration not supported on this platform') + def upload_media(self, filename, progress=None): + raise exc.UnsupportedFunctionality( + 'Remote media upload not supported on this platform') + def _do_web_request(self, url, payload=None, method=None, cache=True): res = None if cache and payload is None and method is None: diff --git a/pyghmi/redfish/oem/lenovo/xcc.py b/pyghmi/redfish/oem/lenovo/xcc.py index c6806e39..883a76d0 100644 --- a/pyghmi/redfish/oem/lenovo/xcc.py +++ b/pyghmi/redfish/oem/lenovo/xcc.py @@ -22,6 +22,8 @@ import time import pyghmi.ipmi.private.util as util import pyghmi.exceptions as pygexc import pyghmi.storage as storage +import pyghmi.util.webclient as webclient +import random class OEMHandler(generic.OEMHandler): @@ -79,7 +81,7 @@ class OEMHandler(generic.OEMHandler): except ValueError: pass yield ('{0} {1}'.format(aname, fname), bdata) - + def _get_disk_firmware_single(self, diskent, prefix=''): bdata = {} if not prefix and diskent.get('location', '').startswith('M.2'): @@ -178,6 +180,15 @@ class OEMHandler(generic.OEMHandler): 'Unexpected return to set disk state: {0}'.format( rsp.get('return', -1))) + def _refresh_token(self): + self._refresh_token_wc(self.wc) + + def _refresh_token_wc(self, wc): + wc.grab_json_response('/api/providers/identity') + if '_csrf_token' in wc.cookies: + wc.set_header('X-XSRF-TOKEN', self.wc.cookies['_csrf_token']) + wc.vintage = util._monotonic_time() + def _make_available(self, disk, realcfg): # 8 if jbod, 4 if hotspare.., leave alone if already... currstatus = self._get_status(disk, realcfg) @@ -414,3 +425,48 @@ class OEMHandler(generic.OEMHandler): if '_csrf_token' in wc.cookies: wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token']) return wc + + def upload_media(self, filename, progress=None): + xid = random.randint(0, 1000000000) + self._refresh_token() + uploadthread = webclient.FileUploader( + self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, None) + uploadthread.start() + while uploadthread.isAlive(): + uploadthread.join(3) + rsp = self.wc.grab_json_response( + '/upload/progress?X-Progress-ID={0}'.format(xid)) + if progress and rsp['state'] == 'uploading': + progress({'phase': 'upload', + 'progress': 100.0 * rsp['received'] / rsp['size']}) + self._refresh_token() + rsp = json.loads(uploadthread.rsp) + if progress: + progress({'phase': 'upload', + 'progress': 100.0}) + thepath = rsp['items'][0]['path'] + thename = rsp['items'][0]['name'] + writeable = 1 if filename.lower().endswith('.img') else 0 + addfile = {"Url": thepath, "Protocol": 6, "Write": writeable, + "Credential": ":", "Option": "", "Domain": "", + "WebUploadName": thename} + rsp = self.wc.grab_json_response('/api/providers/rp_rdoc_addfile', + addfile) + self._refresh_token() + if rsp.get('return', -1) != 0: + errmsg = repr(rsp) if rsp else self.wc.lastjsonerror + raise Exception('Unrecognized return: ' + errmsg) + rsp = self.wc.grab_json_response('/api/providers/rp_rdoc_getfiles') + if 'items' not in rsp or len(rsp['items']) == 0: + raise Exception( + 'Image upload was not accepted, it may be too large') + self._refresh_token() + rsp = self.wc.grab_json_response('/api/providers/rp_rdoc_mountall', + {}) + self._refresh_token() + if rsp.get('return', -1) != 0: + errmsg = repr(rsp) if rsp else self.wc.lastjsonerror + raise Exception('Unrecognized return: ' + errmsg) + if progress: + progress({'phase': 'complete'}) + #self.weblogout()