mirror of
https://opendev.org/x/pyghmi
synced 2025-07-18 02:01:10 +00:00
Apply firmware update through XCC
Automate from API the process of applying a firmware update to XCC. Change-Id: If32044431380b90ac47e1b3642fb5013da199f70
This commit is contained in:
@@ -1765,6 +1765,20 @@ class Command(object):
|
||||
self.oem_init()
|
||||
return self._oem.get_graphical_console()
|
||||
|
||||
def update_firmware(self, file, data=None, progress=None):
|
||||
"""Send file to BMC to perform firmware update
|
||||
|
||||
:param filename: The filename to upload to the target BMC
|
||||
:param data: The payload of the firmware. Default is to read from
|
||||
specified filename.
|
||||
:param progress: A callback that will be given a dict describing
|
||||
update process. Provide if
|
||||
"""
|
||||
self.oem_init()
|
||||
if progress is None:
|
||||
progress = lambda x: True
|
||||
self._oem.update_firmware(file, data, progress)
|
||||
|
||||
def attach_remote_media(self, url, username=None, password=None):
|
||||
"""Attach remote media by url
|
||||
|
||||
|
@@ -198,6 +198,10 @@ class OEMHandler(object):
|
||||
"""
|
||||
return ()
|
||||
|
||||
def update_firmware(self, filename, data=None, progress=None):
|
||||
raise exc.UnsupportedFunctionality(
|
||||
'Firmware update not supported on this platform')
|
||||
|
||||
def get_graphical_console(self):
|
||||
"""Get graphical console launcher"""
|
||||
return ()
|
||||
|
@@ -828,6 +828,13 @@ class OEMHandler(generic.OEMHandler):
|
||||
else:
|
||||
raise
|
||||
|
||||
def update_firmware(self, filename, data=None, progress=None):
|
||||
if self.has_xcc:
|
||||
return self.immhandler.update_firmware(
|
||||
filename, data=data, progress=progress)
|
||||
super(OEMHandler, self).update_firmware(filename, data=data,
|
||||
progress=progress)
|
||||
|
||||
def detach_remote_media(self):
|
||||
if self.has_imm:
|
||||
self.immhandler.detach_remote_media()
|
||||
|
@@ -16,12 +16,28 @@
|
||||
|
||||
from datetime import datetime
|
||||
import json
|
||||
import pyghmi.ipmi.private.session as ipmisession
|
||||
import pyghmi.ipmi.private.util as util
|
||||
import pyghmi.util.webclient as webclient
|
||||
import random
|
||||
import threading
|
||||
import urllib
|
||||
import weakref
|
||||
|
||||
|
||||
class FileUploader(threading.Thread):
|
||||
|
||||
def __init__(self, webclient, url, filename, data):
|
||||
self.wc = webclient
|
||||
self.url = url
|
||||
self.filename = filename
|
||||
self.data = data
|
||||
super(FileUploader, self).__init__()
|
||||
|
||||
def run(self):
|
||||
self.rsp = self.wc.upload(self.url, self.filename, self.data)
|
||||
|
||||
|
||||
class IMMClient(object):
|
||||
logouturl = '/data/logout'
|
||||
bmcname = 'IMM'
|
||||
@@ -480,3 +496,95 @@ class XCCClient(IMMClient):
|
||||
json.dumps({'Slot': slot}))
|
||||
if 'return' not in rt or rt['return'] != 0:
|
||||
raise Exception("Unrecognized return: " + repr(rt))
|
||||
|
||||
def update_firmware(self, filename, data=None, progress=None):
|
||||
try:
|
||||
self.update_firmware_backend(filename, data, progress)
|
||||
except Exception:
|
||||
self.wc.grab_json_response('/api/providers/fwupdate', json.dumps(
|
||||
{'UPD_WebCancel': 1}))
|
||||
raise
|
||||
|
||||
def update_firmware_backend(self, filename, data=None, progress=None):
|
||||
rsv = self.wc.grab_json_response('/api/providers/fwupdate', json.dumps(
|
||||
{'UPD_WebReserve': 1}))
|
||||
if rsv['return'] != 0:
|
||||
raise Exception('Unexpected return to reservation: ' + repr(rsv))
|
||||
xid = random.randint(0, 1000000000)
|
||||
uploadthread = FileUploader(self.wc.dupe(),
|
||||
'/upload?X-Progress-ID={0}'.format(xid),
|
||||
filename, data)
|
||||
uploadthread.start()
|
||||
uploadstate = None
|
||||
while uploadthread.isAlive():
|
||||
uploadthread.join(3)
|
||||
rsp = self.wc.grab_json_response(
|
||||
'/upload/progress?X-Progress-ID={0}'.format(xid))
|
||||
if rsp['state'] == 'uploading':
|
||||
progress({'phase': 'upload',
|
||||
'progress': 100.0 * rsp['received'] / rsp['size']})
|
||||
elif rsp['state'] != 'done':
|
||||
raise Exception('Unexpected result:' + repr(rsp))
|
||||
uploadstate = rsp['state']
|
||||
self.wc.grab_json_response('/api/providers/identity')
|
||||
while uploadstate != 'done':
|
||||
rsp = self.wc.grab_json_response(
|
||||
'/upload/progress?X-Progress-ID={0}'.format(xid))
|
||||
uploadstate = rsp['state']
|
||||
self.wc.grab_json_response('/api/providers/identity')
|
||||
rsp = json.loads(uploadthread.rsp)
|
||||
if rsp['items'][0]['name'] != filename:
|
||||
raise Exception('Unexpected response: ' + repr(rsp))
|
||||
progress({'phase': 'upload',
|
||||
'progress': 100.0})
|
||||
self.wc.grab_json_response('/api/providers/identity')
|
||||
if '_csrf_token' in self.wc.cookies:
|
||||
self.wc.set_header('X-XSRF-TOKEN', self.wc.cookies['_csrf_token'])
|
||||
rsp = self.wc.grab_json_response('/api/providers/fwupdate', json.dumps(
|
||||
{'UPD_WebSetFileName': rsp['items'][0]['path']}))
|
||||
if rsp['return'] != 0:
|
||||
raise Exception('Unexpected return to set filename: ' + repr(rsp))
|
||||
rsp = self.wc.grab_json_response('/api/providers/fwupdate', json.dumps(
|
||||
{'UPD_WebVerifyUploadFile': 1}))
|
||||
if rsp['return'] != 0:
|
||||
raise Exception('Unexpected return to verify: ' + repr(rsp))
|
||||
self.wc.grab_json_response('/api/providers/identity')
|
||||
rsp = self.wc.grab_json_response(
|
||||
'/upload/progress?X-Progress-ID={0}'.format(xid))
|
||||
if rsp['state'] != 'done':
|
||||
raise Exception('Unexpected progress: ' + repr(rsp))
|
||||
rsp = self.wc.grab_json_response('/api/dataset/imm_firmware_success')
|
||||
if len(rsp['items']) != 1:
|
||||
raise Exception('Unexpected result: ' + repr(rsp))
|
||||
rsp = self.wc.grab_json_response('/api/dataset/imm_firmware_update')
|
||||
if rsp['items'][0]['upgrades'][0]['id'] != 1:
|
||||
raise Exception('Unexpected answer: ' + repr(rsp))
|
||||
if '_csrf_token' in self.wc.cookies:
|
||||
self.wc.set_header('X-XSRF-TOKEN', self.wc.cookies['_csrf_token'])
|
||||
rsp = self.wc.grab_json_response('/api/providers/fwupdate', json.dumps(
|
||||
{'UPD_WebStartDefaultAction': 1}))
|
||||
if rsp['return'] != 0:
|
||||
raise Exception('Unexpected result starting update: ' +
|
||||
rsp['return'])
|
||||
complete = False
|
||||
while not complete:
|
||||
ipmisession.Session.pause(3)
|
||||
rsp = self.wc.grab_json_response(
|
||||
'/api/dataset/imm_firmware_progress')
|
||||
progress({'phase': 'apply',
|
||||
'progress': rsp['items'][0]['action_percent_complete']})
|
||||
if rsp['items'][0]['action_state'] == 'Idle':
|
||||
complete = True
|
||||
break
|
||||
if rsp['items'][0]['action_state'] == 'Complete OK':
|
||||
complete = True
|
||||
if rsp['items'][0]['action_status'] != 0:
|
||||
raise Exception('Unexpected failure: ' + repr(rsp))
|
||||
break
|
||||
if (rsp['items'][0]['action_state'] == 'In Progress' and
|
||||
rsp['items'][0]['action_status'] == 2):
|
||||
raise Exception('Unexpected failure: ' + repr(rsp))
|
||||
if rsp['items'][0]['action_state'] != 'In Progress':
|
||||
raise Exception(
|
||||
'Unknown condition waiting for '
|
||||
'firmware update: ' + repr(rsp))
|
||||
|
@@ -547,7 +547,7 @@ class Session(object):
|
||||
myport = self.socket.getsockname()[1]
|
||||
for sockaddr in self.allsockaddrs:
|
||||
if (sockaddr in Session.bmc_handlers and
|
||||
myport in Session.bmc_hansdlers[sockaddr]):
|
||||
myport in Session.bmc_handlers[sockaddr]):
|
||||
del Session.bmc_handlers[sockaddr][myport]
|
||||
if Session.bmc_handlers[sockaddr] == {}:
|
||||
del Session.bmc_handlers[sockaddr]
|
||||
|
@@ -31,18 +31,49 @@ except ImportError:
|
||||
__author__ = 'jjohnson2'
|
||||
|
||||
|
||||
# Used as the separator for form data
|
||||
BND = 'TbqbLUSn0QFjx9gxiQLtgBK4Zu6ehLqtLs4JOBS50EgxXJ2yoRMhTrmRXxO1lkoAQdZx16'
|
||||
|
||||
# We will frequently be dealing with the same data across many instances,
|
||||
# consolidate forms to single memory location to get benefits..
|
||||
uploadforms = {}
|
||||
|
||||
|
||||
def get_upload_form(filename, data):
|
||||
try:
|
||||
return uploadforms[filename]
|
||||
except KeyError:
|
||||
form = '--' + BND + '\r\nContent-Disposition: form-data; ' \
|
||||
'name="{0}"; filename="{0}"\r\n'.format(filename)
|
||||
form += 'Content-Type: application/octet-stream\r\n\r\n' + data
|
||||
form += '\r\n--' + BND + '--\r\n'
|
||||
uploadforms[filename] = form
|
||||
return form
|
||||
|
||||
|
||||
class SecureHTTPConnection(httplib.HTTPConnection, object):
|
||||
default_port = httplib.HTTPS_PORT
|
||||
|
||||
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
||||
ca_certs=None, strict=None, verifycallback=None, **kwargs):
|
||||
ca_certs=None, strict=None, verifycallback=None, clone=None,
|
||||
**kwargs):
|
||||
if 'timeout' not in kwargs:
|
||||
kwargs['timeout'] = 60
|
||||
self.thehost = host
|
||||
self.theport = port
|
||||
httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs)
|
||||
self.cert_reqs = ssl.CERT_NONE # verification will be done ssh style..
|
||||
self._certverify = verifycallback
|
||||
self.cookies = {}
|
||||
self.stdheaders = {}
|
||||
if clone:
|
||||
self._certverify = clone._certverify
|
||||
self.cookies = clone.cookies.copy()
|
||||
self.stdheaders = clone.stdheaders.copy()
|
||||
else:
|
||||
self._certverify = verifycallback
|
||||
self.cookies = {}
|
||||
self.stdheaders = {}
|
||||
|
||||
def dupe(self):
|
||||
return SecureHTTPConnection(self.thehost, self.theport, clone=self)
|
||||
|
||||
def set_header(self, key, value):
|
||||
self.stdheaders[key] = value
|
||||
@@ -76,6 +107,33 @@ class SecureHTTPConnection(httplib.HTTPConnection, object):
|
||||
rsp.read()
|
||||
return {}
|
||||
|
||||
def upload(self, url, filename, data=None):
|
||||
"""Upload a file to the url
|
||||
|
||||
:param url:
|
||||
:param filename: The name of the file
|
||||
:param data: A file object or data to use rather than reading from
|
||||
the file.
|
||||
:return:
|
||||
"""
|
||||
if data is None:
|
||||
data = open(filename, 'rb')
|
||||
if isinstance(data, file):
|
||||
data = data.read()
|
||||
form = get_upload_form(filename, data)
|
||||
ulheaders = self.stdheaders.copy()
|
||||
ulheaders['Content-Type'] = 'multipart/form-data; boundary=' + BND
|
||||
self.request('POST', url, form, ulheaders)
|
||||
rsp = self.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
|
||||
del uploadforms[filename]
|
||||
if rsp.status != 200:
|
||||
raise Exception('Unexpected response in file upload: ' +
|
||||
rsp.read())
|
||||
return rsp.read()
|
||||
|
||||
def request(self, method, url, body=None, headers=None):
|
||||
if headers is None:
|
||||
headers = self.stdheaders.copy()
|
||||
|
Reference in New Issue
Block a user