diff --git a/releasenotes/notes/fix-extended-info-error-handling-73fecb6bf5c852ff.yaml b/releasenotes/notes/fix-extended-info-error-handling-73fecb6bf5c852ff.yaml new file mode 100644 index 00000000..8ee28a4f --- /dev/null +++ b/releasenotes/notes/fix-extended-info-error-handling-73fecb6bf5c852ff.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes ``AttributeError: 'str' object has no attribute 'get'`` during error + handling. This occurs when BMC does not return a list of messages inside + ``@Message.ExtendedInfo``, but a single item. This has been observed with + iDRAC. diff --git a/sushy/exceptions.py b/sushy/exceptions.py index 9f552b81..e8ef17d2 100644 --- a/sushy/exceptions.py +++ b/sushy/exceptions.py @@ -101,9 +101,9 @@ class HTTPError(SushyError): self.code = self.body.get('code', 'Base.1.0.GeneralError') self.detail = self.body.get('message') ext_info = self.body.get('@Message.ExtendedInfo', [{}]) - index = self._get_most_severe_msg_index(ext_info) - self.detail = ext_info[index].get('Message', self.detail) - error = '%s: %s' % (self.code, self.detail or 'unknown error') + message = self._get_most_severe_msg(ext_info) + self.detail = message or self.detail + error = '%s: %s' % (self.code, self.detail or 'unknown error.') kwargs = {'method': method, 'url': url, 'code': self.status_code, 'error': error} @@ -112,13 +112,14 @@ class HTTPError(SushyError): super(HTTPError, self).__init__(**kwargs) @staticmethod - def _get_most_severe_msg_index(extended_info): + def _get_most_severe_msg(extended_info): + if not isinstance(extended_info, list): + return extended_info.get('Message', None) if len(extended_info) > 0: for sev in ['Critical', 'Warning']: for i, m in enumerate(extended_info): if m.get('Severity') == sev: - return i - return 0 + return m.get('Message') class BadRequestError(HTTPError): diff --git a/sushy/tests/unit/json_samples/error_single_ext_info.json b/sushy/tests/unit/json_samples/error_single_ext_info.json new file mode 100644 index 00000000..d56a0a6e --- /dev/null +++ b/sushy/tests/unit/json_samples/error_single_ext_info.json @@ -0,0 +1,13 @@ +{ + "error": { + "code": "Base.1.5.GeneralError", + "message": "A general error has occurred. See ExtendedInfo for more information.", + "@Message.ExtendedInfo": { + "@odata.type": "#Message.v1_0_0.Message", + "MessageId": "Base.1.5.GeneralError", + "Message": "A general error has occurred. See Resolution for information on how to resolve the error.", + "Resolution": "Redfish request contains unsupported media type. Correct the request body and resubmit.", + "Severity": "Warning" + } + } +} \ No newline at end of file diff --git a/sushy/tests/unit/test_connector.py b/sushy/tests/unit/test_connector.py index d788a519..28a5e56f 100644 --- a/sushy/tests/unit/test_connector.py +++ b/sushy/tests/unit/test_connector.py @@ -255,6 +255,21 @@ class ConnectorOpTestCase(base.TestCase): self.assertIsNotNone(exc.body) self.assertIn('body submitted was malformed JSON', exc.detail) + def test_known_http_error_nonlist_ext_info(self): + self.request.return_value.status_code =\ + http_client.UNSUPPORTED_MEDIA_TYPE + with open('sushy/tests/unit/json_samples/' + 'error_single_ext_info.json') as f: + self.request.return_value.json.return_value = json.load(f) + + with self.assertRaisesRegex(exceptions.HTTPError, + 'See Resolution for information') as cm: + self.conn._op('POST', 'http://foo.bar') + exc = cm.exception + self.assertEqual(http_client.UNSUPPORTED_MEDIA_TYPE, exc.status_code) + self.assertIsNotNone(exc.body) + self.assertIn('See Resolution for information', exc.detail) + def test_not_found_error(self): self.request.return_value.status_code = http_client.NOT_FOUND self.request.return_value.json.side_effect = ValueError('no json')