mirror of
https://opendev.org/x/pyghmi
synced 2025-08-17 16:40:19 +00:00
Add Update of TSM for SR635/SR655
This allows update of TSM and UEFI firmware Change-Id: I8d32fddd169805c0f5b13ecbb553d4b4272e334b
This commit is contained in:
@@ -37,6 +37,7 @@ from pyghmi.ipmi.oem.lenovo import pci
|
||||
from pyghmi.ipmi.oem.lenovo import psu
|
||||
from pyghmi.ipmi.oem.lenovo import raid_controller
|
||||
from pyghmi.ipmi.oem.lenovo import raid_drive
|
||||
from pyghmi.ipmi.oem.lenovo import tsma
|
||||
|
||||
|
||||
import pyghmi.util.webclient as wc
|
||||
@@ -156,6 +157,8 @@ class OEMHandler(generic.OEMHandler):
|
||||
self.immhandler = imm.IMMClient(ipmicmd)
|
||||
elif self.is_fpc:
|
||||
self.smmhandler = nextscale.SMMClient(ipmicmd)
|
||||
elif self.has_tsma:
|
||||
self.tsmahandler = tsma.TsmHandler(ipmicmd)
|
||||
|
||||
@property
|
||||
def _megarac_eth_index(self):
|
||||
@@ -355,6 +358,13 @@ class OEMHandler(generic.OEMHandler):
|
||||
self.oemid['device_id'],
|
||||
self.oemid['product_id'])
|
||||
|
||||
tsma_ids = ((19046, 32, 1287),)
|
||||
@property
|
||||
def has_tsma(self):
|
||||
currid = (self.oemid['manufacturer_id'], self.oemid['device_id'],
|
||||
self.oemid['product_id'])
|
||||
return currid in self.tsma_ids
|
||||
|
||||
@property
|
||||
def has_tsm(self):
|
||||
"""True if this particular server have a TSM based service processor
|
||||
@@ -927,6 +937,9 @@ class OEMHandler(generic.OEMHandler):
|
||||
if self.is_fpc:
|
||||
return self.smmhandler.update_firmware(
|
||||
filename, data=data, progress=progress, bank=bank)
|
||||
if self.has_tsma:
|
||||
return self.tsmahandler.update_firmware(
|
||||
filename, data=data, progress=progress, bank=bank)
|
||||
return super(OEMHandler, self).update_firmware(filename, data=data,
|
||||
progress=progress,
|
||||
bank=bank)
|
||||
|
262
pyghmi/ipmi/oem/lenovo/tsma.py
Normal file
262
pyghmi/ipmi/oem/lenovo/tsma.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import pyghmi.util.webclient as webclient
|
||||
import struct
|
||||
import time
|
||||
import urllib
|
||||
import weakref
|
||||
|
||||
hpm_by_filename = {}
|
||||
|
||||
class HpmSection(object):
|
||||
__slots__ = ['comp_id', 'comp_ver', 'comp_name', 'section_flash', 'data', 'hash_size', 'combo_image']
|
||||
|
||||
def read_hpm(filename):
|
||||
hpminfo = []
|
||||
with open(filename, 'rb') as hpmfile:
|
||||
hpmfile.seek(0x20)
|
||||
skip = struct.unpack('>H', hpmfile.read(2))[0]
|
||||
hpmfile.seek(skip + 1, 1)
|
||||
sectype, compid = struct.unpack('BB', hpmfile.read(2))
|
||||
while sectype == 2:
|
||||
currsec = HpmSection()
|
||||
currsec.comp_id = compid
|
||||
hpmfile.seek(1, 1)
|
||||
major, minor, pat = struct.unpack('<BBI', hpmfile.read(6))
|
||||
currsec.comp_ver = '{0}.{1}.{2}'.format(major, minor, pat)
|
||||
currsec.comp_name = hpmfile.read(21).rstrip('\x00')
|
||||
currlen = struct.unpack('<I', hpmfile.read(4))[0] - 16
|
||||
oemstr = hpmfile.read(4)
|
||||
if oemstr != b'OEM\x00':
|
||||
raise Exception('Unrecognized HPM field near {0}'.format(hpmfile.tell()))
|
||||
currsec.section_flash = struct.unpack('<I', hpmfile.read(4))[0]
|
||||
hashpresent, hdrsize, blocks = struct.unpack('BBB', hpmfile.read(3))
|
||||
if hashpresent != 1:
|
||||
hashpresent = 0
|
||||
currsec.hash_size = hashpresent * (256 * blocks + hdrsize)
|
||||
hpmfile.seek(5, 1)
|
||||
currsec.data = hpmfile.read(currlen)
|
||||
hpminfo.append(currsec)
|
||||
sectype, compid = struct.unpack('BB', hpmfile.read(2))
|
||||
upimg = hpminfo[1].data[:-hpminfo[1].hash_size] + hpminfo[2].data[:-hpminfo[2].hash_size]
|
||||
hpminfo[2].combo_image = upimg
|
||||
hpminfo[1].combo_image = upimg
|
||||
currpos = hpmfile.tell()
|
||||
hpmfile.seek(0, 2)
|
||||
endpos = hpmfile.tell()
|
||||
if currpos < (endpos - 512):
|
||||
raise Exception("Unexpected end of HPM file")
|
||||
return hpminfo
|
||||
|
||||
|
||||
|
||||
class TsmHandler(object):
|
||||
def __init__(self, ipmicmd):
|
||||
self.ipmicmd = weakref.proxy(ipmicmd)
|
||||
self.tsm = ipmicmd.bmc
|
||||
self.username = ipmicmd.ipmi_session.userid.decode('utf-8')
|
||||
self.password = ipmicmd.ipmi_session.password.decode('utf-8')
|
||||
self._wc = None
|
||||
self.csrftok = None
|
||||
|
||||
@property
|
||||
def wc(self):
|
||||
self.fwid = None
|
||||
if self._wc:
|
||||
return self._wc
|
||||
authdata = {
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
}
|
||||
wc = webclient.SecureHTTPConnection(self.tsm, 443, verifycallback=self.ipmicmd.certverify, timeout=180)
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if status < 200 or status >= 300:
|
||||
raise Exception('Error establishing web session')
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
wc.set_header('X-CSRFTOKEN', self.csrftok)
|
||||
self._wc = wc
|
||||
return wc
|
||||
|
||||
def update_firmware(self, filename, data=None, progress=None, bank=None):
|
||||
wc = self.wc
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
if filename.endswith('.hpm'):
|
||||
self.update_hpm_firmware(filename, progress, wc)
|
||||
elif 'uefi' in filename and filename.endswith('.rom'):
|
||||
self.update_uefi_firmware(filename, progress, wc)
|
||||
else:
|
||||
raise Exception('Unsupported filename {0}'.format(filename))
|
||||
|
||||
def update_uefi_firmware(self, filename, progress, wc):
|
||||
rsp = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/BIOSremoteSave', {"tftpip":"","tftpfile":""})
|
||||
hdrs = wc.stdheaders.copy()
|
||||
hdrs['Content-Length'] = 0
|
||||
rsp = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/flash', method='PUT', headers=hdrs)
|
||||
fu = webclient.FileUploader(
|
||||
wc, '/api/maintenance/firmware/BIOS', filename, formname='fwimage')
|
||||
fu.start()
|
||||
while fu.isAlive():
|
||||
fu.join(3)
|
||||
if progress:
|
||||
progress({
|
||||
'phase': 'upload',
|
||||
'progress': 100 * wc.get_upload_progress()})
|
||||
if progress:
|
||||
progress({
|
||||
'phase': 'apply',
|
||||
'progress': 0.0})
|
||||
rsp = wc.grab_json_response_with_status('/api/maintenance/BIOSstart')
|
||||
applypct = 0.0
|
||||
if rsp[1] >= 200 and rsp[1] < 300 and rsp[0]['wRet'] == 0:
|
||||
updone = False
|
||||
while not updone:
|
||||
rsp = wc.grab_json_response('/api/maintenance/BIOSstatus')
|
||||
if rsp.get('state', 0) == 9:
|
||||
break
|
||||
if rsp.get('state', 0) in (6, 10):
|
||||
raise Exception('Update Failure')
|
||||
if (rsp.get('state', 0) == 8 and rsp.get('progress', 0) > 0
|
||||
and progress):
|
||||
progress({
|
||||
'phase': 'apply',
|
||||
'progress': 70 + float(rsp.get('progress', 0))/100*30})
|
||||
elif progress and applypct < 70:
|
||||
applypct += 1.4
|
||||
progress({'phase': 'apply', 'progress': applypct})
|
||||
return 'pending'
|
||||
raise Exception('Update Failure')
|
||||
|
||||
def update_hpm_firmware(self, filename, progress, wc):
|
||||
rsp = wc.grab_json_response('/api/maintenance/hpm/freemem')
|
||||
if 'MemFree' not in rsp:
|
||||
raise Exception('System Not Ready for update')
|
||||
if filename not in hpm_by_filename:
|
||||
hpminfo = read_hpm(filename)
|
||||
if len(hpminfo) != 3:
|
||||
raise Exception(
|
||||
'This HPM update is currently not supported')
|
||||
hpm_by_filename[filename] = read_hpm(filename)
|
||||
else:
|
||||
hpminfo = hpm_by_filename[filename]
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/updatemode', method='PUT')
|
||||
# first segment, make sure it is mmc,
|
||||
# then do the preparecomponents with the following payload
|
||||
if status != 200:
|
||||
raise Exception(rsp)
|
||||
uid = rsp['unique_id']
|
||||
self.fwid = uid
|
||||
payload = {
|
||||
'FWUPDATEID': uid,
|
||||
'COMPONENT_ID': 1,
|
||||
'COMPONENT_DATA_LEN': len(hpminfo[0].data),
|
||||
'IS_MMC': 1,
|
||||
}
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/preparecomponents', payload,
|
||||
referer='https://{0}/'.format(self.tsm), method='PUT')
|
||||
if status < 200 or status >= 300:
|
||||
err = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/exitupdatemode', {'FWUPDATEID': uid},
|
||||
method='PUT')
|
||||
raise Exception(rsp)
|
||||
fu = webclient.FileUploader(
|
||||
wc, '/api/maintenance/hpm/mmcfw', 'blob', hpminfo[0].data, 'mmc')
|
||||
if progress:
|
||||
progress({'phase': 'upload', 'progress': 0.0})
|
||||
fu.start()
|
||||
while fu.isAlive():
|
||||
fu.join(3)
|
||||
if progress:
|
||||
progress({
|
||||
'phase': 'upload',
|
||||
'progress': 50 * wc.get_upload_progress()})
|
||||
del payload['IS_MMC']
|
||||
payload['SECTION_FLASH'] = hpminfo[0].section_flash
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/flash', payload, method='PUT')
|
||||
percent = 0
|
||||
while percent < 100:
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/upgradestatus?COMPONENT_ID=1')
|
||||
if status < 200 or status >= 300:
|
||||
raise Exception(rsp)
|
||||
percent = rsp['PROGRESS']
|
||||
if progress:
|
||||
progress({
|
||||
'phase': 'apply',
|
||||
'progress': .5 * percent})
|
||||
if percent < 100:
|
||||
time.sleep(3)
|
||||
if progress:
|
||||
progress({'phase': 'validating', 'progress': 0.0})
|
||||
del payload['SECTION_FLASH']
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/verifyimage', payload, method='PUT')
|
||||
percent = 0
|
||||
while percent < 100:
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/verifyimagestatus?COMPONENT_ID=1')
|
||||
if status < 200 or status >= 300:
|
||||
raise Exception(rsp)
|
||||
percent = rsp['PROGRESS']
|
||||
if progress:
|
||||
progress({
|
||||
'phase': 'validating',
|
||||
'progress': 0.5 * percent})
|
||||
if percent < 100:
|
||||
time.sleep(3)
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/hpm/exitupdatemode', {'FWUPDATEID': uid},
|
||||
method='PUT')
|
||||
fu = webclient.FileUploader(wc, '/api/maintenance/firmware/firmware',
|
||||
'blob', hpminfo[1].combo_image, 'fwimage')
|
||||
fu.start()
|
||||
while fu.isAlive():
|
||||
fu.join(3)
|
||||
if progress:
|
||||
progress({
|
||||
'phase': 'upload',
|
||||
'progress': 50 * wc.get_upload_progress() + 50})
|
||||
rsp = wc.grab_json_response('/api/maintenance/firmware/verification')
|
||||
upgradeparms = {
|
||||
'preserve_config': 1,
|
||||
'flash_status': 1,
|
||||
}
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/maintenance/firmware/upgrade',
|
||||
upgradeparms, method='PUT')
|
||||
if progress:
|
||||
progress({'phase': 'apply', 'progress': 50.0})
|
||||
applied = False
|
||||
while not applied:
|
||||
rsp = wc.grab_json_response(
|
||||
'/api/maintenance/firmware/flash-progress')
|
||||
percent = float(rsp['progress'].split('%')[0])
|
||||
percent = percent * 0.5 + 50
|
||||
if progress:
|
||||
progress({'phase': 'apply', 'progress': percent})
|
||||
if rsp['progress'] == '100% done' and rsp['state'] == 0:
|
||||
applied = True
|
||||
break
|
||||
time.sleep(3)
|
||||
hdrs = wc.stdheaders.copy()
|
||||
hdrs['Content-Length'] = 0
|
||||
rsp = wc.grab_json_response_with_status('/api/maintenance/reset', method='POST', headers=hdrs)
|
||||
return 'complete'
|
@@ -109,6 +109,7 @@ class SecureHTTPConnection(httplib.HTTPConnection, object):
|
||||
**kwargs):
|
||||
if 'timeout' not in kwargs:
|
||||
kwargs['timeout'] = 60
|
||||
self.mytimeout = kwargs['timeout']
|
||||
self._currdl = None
|
||||
self.lastjsonerror = None
|
||||
self.broken = False
|
||||
@@ -132,7 +133,8 @@ class SecureHTTPConnection(httplib.HTTPConnection, object):
|
||||
self.stdheaders['Host'] = '[' + host[:host.find('%')] + ']'
|
||||
|
||||
def dupe(self):
|
||||
return SecureHTTPConnection(self.thehost, self.theport, clone=self)
|
||||
return SecureHTTPConnection(self.thehost, self.theport, clone=self,
|
||||
timeout=self.mytimeout)
|
||||
|
||||
def set_header(self, key, value):
|
||||
self.stdheaders[key] = value
|
||||
@@ -146,7 +148,7 @@ class SecureHTTPConnection(httplib.HTTPConnection, object):
|
||||
# workaround problems of too large mtu, moderately frequent occurance
|
||||
# in this space
|
||||
plainsock = socket.socket(addrinfo[0])
|
||||
plainsock.settimeout(60)
|
||||
plainsock.settimeout(self.mytimeout)
|
||||
try:
|
||||
plainsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
|
||||
except socket.error:
|
||||
|
Reference in New Issue
Block a user