From d665064dbd9961435f1a0db7c3e31af672a7e0ff Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 27 Jul 2026 14:58:58 +0200 Subject: [PATCH] Hold the SMM web session across long operations A firmware update posts on one session for the minutes its apply loop runs, and an FFDC collection downloads on the session it acquired, but wc() judges a session by its age alone, so a settings call arriving thirty seconds in logged that session out from underneath them. Flag the long operations the way the IMM and XCC handlers already do and leave their session in place. --- .../aiohmi/ipmi/oem/lenovo/nextscale.py | 130 ++++++++++-------- 1 file changed, 74 insertions(+), 56 deletions(-) diff --git a/confluent_server/aiohmi/ipmi/oem/lenovo/nextscale.py b/confluent_server/aiohmi/ipmi/oem/lenovo/nextscale.py index e8824a2d..7fa1e451 100644 --- a/confluent_server/aiohmi/ipmi/oem/lenovo/nextscale.py +++ b/confluent_server/aiohmi/ipmi/oem/lenovo/nextscale.py @@ -385,6 +385,7 @@ class SMMClient(object): self.password = ipmicmd.ipmi_session.password self._wc = None self.weblogging = False + self.updating = False async def clear_bmc_configuration(self): await self.ipmicmd.raw_command(0x32, 0xad) @@ -798,15 +799,20 @@ class SMMClient(object): url = '/preview/smm-ffdc.tgz?ST1={0}'.format(wc.st1) if autosuffix and not savefile.endswith('.tgz'): savefile += '-smm-ffdc.tgz' - fd = webclient.make_downloader(wc, url, savefile) - while not fd.completed(): - try: - await fd.join(1) - except asyncio.TimeoutError: - pass - if progress and await fd.get_progress(): - progress({'phase': 'download', - 'progress': 100 * await fd.get_progress()}) + # the download runs on this session, keep wc() from logging it out + self.updating = True + try: + fd = webclient.make_downloader(wc, url, savefile) + while not fd.completed(): + try: + await fd.join(1) + except asyncio.TimeoutError: + pass + if progress and await fd.get_progress(): + progress({'phase': 'download', + 'progress': 100 * await fd.get_progress()}) + finally: + self.updating = False if progress: progress({'phase': 'complete'}) return savefile @@ -985,53 +991,63 @@ class SMMClient(object): break progress({'phase': 'upload', 'progress': 0.0}) wc = await self.wc() - rsp, status, _ = await wc.grab_response_with_status('/data', 'set=fwType:10') # SMM firmware - if status != 200: - raise Exception(rsp) - url = '/fwupload/fwupload.esp?ST1={0}'.format(wc.st1) - fu = await webclient.make_uploader( - wc, url, filename, data, formname='fileUpload', - otherfields={'preConfig': 'on'}) - while not fu.completed(): - try: - await fu.join(3) - except asyncio.TimeoutError: - pass - if progress: - progress({'phase': 'upload', - 'progress': 100 * await fu.get_progress()}) - progress({'phase': 'validating', 'progress': 0.0}) - url = '/data' - rsp, status, _ = await wc.grab_response_with_status(url, 'get=fwVersion,spfwInfo') - if status != 200: - raise Exception('Error validating firmware') - progress({'phase': 'apply', 'progress': 0.0}) - # only understood by newer SMM2 firmware, ignore rejection by older - await wc.grab_response_with_status('/data', 'set=securityrollback:1') - rsp, status, _ = await wc.grab_response_with_status('/data', 'set=fwUpdate:1') - if status != 200: - raise Exception(rsp) - complete = False - tries = 0 - while not complete: - await ipmisession.Session.pause(3) - try: - progdata, status, _ = await wc.grab_response_with_status('/data', 'get=fwProgress,fwUpdate') - except Exception: - if tries > 2: - raise - tries += 1 - continue + # the update runs on this session, keep wc() from logging it out + self.updating = True + try: + rsp, status, _ = await wc.grab_response_with_status( + '/data', 'set=fwType:10') # SMM firmware if status != 200: - raise Exception('Error applying firmware') - progdata = fromstring(progdata) - if progdata.findall('fwUpdate')[0].text == 'invalid signature': - raise Exception('Firmware signature invalid') - percent = float(progdata.findall('fwProgress')[0].text) + raise Exception(rsp) + url = '/fwupload/fwupload.esp?ST1={0}'.format(wc.st1) + fu = await webclient.make_uploader( + wc, url, filename, data, formname='fileUpload', + otherfields={'preConfig': 'on'}) + while not fu.completed(): + try: + await fu.join(3) + except asyncio.TimeoutError: + pass + if progress: + progress({'phase': 'upload', + 'progress': 100 * await fu.get_progress()}) + progress({'phase': 'validating', 'progress': 0.0}) + url = '/data' + rsp, status, _ = await wc.grab_response_with_status( + url, 'get=fwVersion,spfwInfo') + if status != 200: + raise Exception('Error validating firmware') + progress({'phase': 'apply', 'progress': 0.0}) + # only understood by newer SMM2 firmware, ignore rejection by older + await wc.grab_response_with_status( + '/data', 'set=securityrollback:1') + rsp, status, _ = await wc.grab_response_with_status( + '/data', 'set=fwUpdate:1') + if status != 200: + raise Exception(rsp) + complete = False + tries = 0 + while not complete: + await ipmisession.Session.pause(3) + try: + progdata, status, _ = await wc.grab_response_with_status( + '/data', 'get=fwProgress,fwUpdate') + except Exception: + if tries > 2: + raise + tries += 1 + continue + if status != 200: + raise Exception('Error applying firmware') + progdata = fromstring(progdata) + if progdata.findall('fwUpdate')[0].text == 'invalid signature': + raise Exception('Firmware signature invalid') + percent = float(progdata.findall('fwProgress')[0].text) - progress({'phase': 'apply', - 'progress': percent}) - complete = percent >= 100.0 + progress({'phase': 'apply', + 'progress': percent}) + complete = percent >= 100.0 + finally: + self.updating = False return 'complete' async def get_inventory_descriptions(self, ipmicmd, variant): @@ -1096,8 +1112,10 @@ class SMMClient(object): try: if (not self._wc or (self._wc.vintage and self._wc.vintage < util._monotonic_time() - 30)): - # in case the existing session is still valid, dispose of it - await self.logout() + if not self.updating and self._wc: + # in case the existing session is still valid, dispose + # of it + await self.logout() self._wc = await self.get_webclient() finally: self.weblogging = False