diff --git a/confluent_server/aiohmi/ipmi/oem/lenovo/config.py b/confluent_server/aiohmi/ipmi/oem/lenovo/config.py index 7ae16c66..35e72d3b 100644 --- a/confluent_server/aiohmi/ipmi/oem/lenovo/config.py +++ b/confluent_server/aiohmi/ipmi/oem/lenovo/config.py @@ -147,8 +147,12 @@ def _eval_conditional(expression, cfg, setting): class LenovoFirmwareConfig(object): def __init__(self, xc, useipmi=True): if not etree: - raise Exception("python-lxml and python-eficompressor required " - "for this function") + # Not a bare Exception. Confluent has no handler for one, so a + # missing optional dependency showed as "Unexpected Error" and hid + # the message below, which already names what to install. + raise pygexc.UnsupportedFunctionality( + "python-lxml and python-eficompressor required " + "for this function") if useipmi: self.connection = xc.ipmicmd else: diff --git a/confluent_server/aiohmi/redfish/oem/generic.py b/confluent_server/aiohmi/redfish/oem/generic.py index 763d0383..953e98ae 100644 --- a/confluent_server/aiohmi/redfish/oem/generic.py +++ b/confluent_server/aiohmi/redfish/oem/generic.py @@ -1797,7 +1797,14 @@ class OEMHandler(object): errmsg = ','.join(errmsg) raise exc.RedfishError(errmsg) except (ValueError, KeyError): - raise exc.PyghmiException(str(url) + ":" + res[0]) + # Reached by an html 404 page, or anything else that is not + # the JSON error document. res[0] is bytes there, so building + # the message crashed and the caller saw that TypeError + # instead of the status and the body. + body = res[0] + if isinstance(body, bytes): + body = body.decode('utf-8', errors='replace') + raise exc.PyghmiException('{0}:{1}'.format(url, body)) if payload is None and method is None: self._urlcache[url] = { 'contents': res[0], diff --git a/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py b/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py index d9a9d173..a2d01fc6 100644 --- a/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py +++ b/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py @@ -1150,6 +1150,18 @@ class OEMHandler(generic.OEMHandler): self.weblogging = False return self._wc + async def wc_if_available(self): + """The web client, or None where carrying on without it is meant. + + Four inventory reads answer partially when the web interface is out of + reach, and used to say so by checking wc() for None. wc() refuses now, + so they ask here instead. + """ + try: + return await self.wc() + except pygexc.UnsupportedFunctionality: + return None + async def get_webclient(self, login=True): wc = self.webclient.dupe() wc.vintage = util._monotonic_time() @@ -1179,6 +1191,12 @@ class OEMHandler(generic.OEMHandler): wc.set_header('X-XSRF-TOKEN', cookie.value) break return wc + # Falling off the end returned None, which wc() handed to thirty + # call sites that use it unchecked. The status separates firmware + # with no web api, answering 404, from credentials it will not take. + raise pygexc.UnsupportedFunctionality( + 'the XCC web interface, which this operation needs beside ' + 'Redfish, did not accept a login (HTTP {0})'.format(status)) async def grab_redfish_response_with_status(self, url, body=None, method=None): wc = self.webclient @@ -1818,7 +1836,7 @@ class OEMHandler(generic.OEMHandler): if self.updating: raise pygexc.TemporaryError( 'Cannot read extended inventory during firmware update') - wc = await self.wc() + wc = await self.wc_if_available() if wc: adapterdata = await wc.grab_json_response(self.ADP_URL) if adapterdata: @@ -1897,7 +1915,7 @@ class OEMHandler(generic.OEMHandler): # mode 0 is firmware, 1 is hardware storagedata = self.get_cached_data('lenovo_cached_storage') if not storagedata: - wc = await self.wc() + wc = await self.wc_if_available() if wc: storagedata = await wc.grab_json_response( '/api/function/raid_alldevices?params=storage_GetAllDisks') @@ -1926,7 +1944,7 @@ class OEMHandler(generic.OEMHandler): async def _get_cpu_inventory(self): procdata = self.get_cached_data('lenovo_cached_proc') if not procdata: - wc = await self.wc() + wc = await self.wc_if_available() if wc: procdata = await wc.grab_json_response( '/api/dataset/imm_processors') @@ -1944,7 +1962,7 @@ class OEMHandler(generic.OEMHandler): async def _get_mem_inventory(self): memdata = self.get_cached_data('lenovo_cached_memory') if not memdata: - wc = await self.wc() + wc = await self.wc_if_available() if wc: memdata = await wc.grab_json_response( '/api/dataset/imm_memory')