2
0
mirror of https://opendev.org/x/pyghmi synced 2025-07-18 10:11: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:
Jarrod Johnson
2017-07-19 16:50:30 -04:00
parent 52f5224ca1
commit 05754f9977
6 changed files with 196 additions and 5 deletions

View File

@@ -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

View File

@@ -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 ()

View File

@@ -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()

View File

@@ -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))

View File

@@ -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]

View File

@@ -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()