diff --git a/pyghmi/ipmi/oem/lenovo/handler.py b/pyghmi/ipmi/oem/lenovo/handler.py index 2091b2e3..cffc5b96 100755 --- a/pyghmi/ipmi/oem/lenovo/handler.py +++ b/pyghmi/ipmi/oem/lenovo/handler.py @@ -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) diff --git a/pyghmi/ipmi/oem/lenovo/tsma.py b/pyghmi/ipmi/oem/lenovo/tsma.py new file mode 100644 index 00000000..4f32a4ca --- /dev/null +++ b/pyghmi/ipmi/oem/lenovo/tsma.py @@ -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('= 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' diff --git a/pyghmi/util/webclient.py b/pyghmi/util/webclient.py index d2040514..52c6cdf4 100644 --- a/pyghmi/util/webclient.py +++ b/pyghmi/util/webclient.py @@ -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: