Merge "Handle a different error code for missing TransferProtocolType"

This commit is contained in:
Zuul 2022-12-06 16:05:33 +00:00 committed by Gerrit Code Review
commit c52873796f
5 changed files with 49 additions and 26 deletions

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Correctly handles error code ``Base.1.5.PropertyMissing`` when dealing with
hardware that requires ``TransferProtocolType`` for virtual media
operations.

View File

@ -96,6 +96,9 @@ class HTTPError(SushyError):
message = ('HTTP %(method)s %(url)s returned code %(code)s. %(error)s '
'Extended information: %(ext_info)s')
extended_info = None
"""Extended information provided in the response."""
def __init__(self, method, url, response):
self.status_code = response.status_code
try:
@ -106,17 +109,16 @@ class HTTPError(SushyError):
{'method': method, 'url': url, 'code':
self.status_code})
error = 'unknown error'
ext_info = 'none'
else:
self.body = body.get('error', {})
self.code = self.body.get('code', 'Base.1.0.GeneralError')
self.detail = self.body.get('message')
ext_info = self.body.get('@Message.ExtendedInfo', [{}])
message = self._get_most_severe_msg(ext_info)
self.extended_info = self.body.get('@Message.ExtendedInfo')
message = self._get_most_severe_msg(self.extended_info or [{}])
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, 'ext_info': ext_info}
'error': error, 'ext_info': self.extended_info}
LOG.debug('HTTP response for %(method)s %(url)s: '
'status code: %(code)s, error: %(error)s, '
'extended: %(ext_info)s', kwargs)
@ -132,6 +134,14 @@ class HTTPError(SushyError):
if m.get('Severity') == sev:
return m.get('Message')
@property
def related_properties(self):
"""List of properties related to the error."""
try:
return self.extended_info[0]['RelatedProperties']
except (IndexError, KeyError, TypeError):
return []
class BadRequestError(HTTPError):
pass

View File

@ -106,17 +106,16 @@ class VirtualMedia(base.ResourceBase):
eject_uri = eject_media.target_uri
return eject_uri, use_patch
def is_transfer_protocol_required(self, response=None):
def is_transfer_protocol_required(self, error=None):
"""Check the response code and body and in case of failure
Try to determine if it happened due to missing TransferProtocolType.
"""
if (response.code == "Base.1.5.ActionParameterMissing"
and response.body is not None):
if ("#/TransferProtocolType" in response.body
["@Message.ExtendedInfo"][0]['RelatedProperties']):
return True
return False
return (
(error.code.endswith(".ActionParameterMissing")
or error.code.endswith(".PropertyMissing"))
and "#/TransferProtocolType" in error.related_properties
)
def insert_media(self, image, inserted=True, write_protected=True,
username=None, password=None, transfer_method=None):
@ -177,8 +176,8 @@ class VirtualMedia(base.ResourceBase):
# due to absence of TransferProtocolType param and if so adding it
try:
self._conn.post(target_uri, data=payload)
except exceptions.HTTPError as response:
if self.is_transfer_protocol_required(response):
except exceptions.HTTPError as error:
if self.is_transfer_protocol_required(error):
if payload['Image'].startswith('https://'):
payload['TransferProtocolType'] = "HTTPS"
elif payload['Image'].startswith('http://'):

View File

@ -16,8 +16,6 @@ from http import client as http_client
import json
from unittest import mock
import requests
import sushy
from sushy import exceptions
from sushy.resources.certificateservice import certificate
@ -178,18 +176,25 @@ class VirtualMediaTestCase(base.TestCase):
headers={"If-Match": 'W/"3d7b8a7360bf2941d"'})
self.assertTrue(self.sys_virtual_media._is_stale)
@mock.patch.object(requests, 'post', autospec=True)
def test_is_transfer_protocol_required(self, mock_post):
def test_is_transfer_protocol_required(self):
with open('sushy/tests/unit/json_samples/'
'transfer_proto_required_error.json') as f:
response_obj = json.load(f)
response = mock.MagicMock()
response.status_code = 400
response.code = "Base.1.5.ActionParameterMissing"
response.body = response_obj['error']
response.raise_for_status.side_effect = requests.exceptions.HTTPError
mock_post.return_value = response
retval = self.sys_virtual_media.is_transfer_protocol_required(response)
response = mock.Mock(spec=['json', 'status_code'])
response.json.return_value = response_obj
error = exceptions.HTTPError('GET', 'VirtualMedia', response)
retval = self.sys_virtual_media.is_transfer_protocol_required(error)
self.assertTrue(retval)
def test_is_transfer_protocol_required_alt_code(self):
with open('sushy/tests/unit/json_samples/'
'transfer_proto_required_error.json') as f:
response_obj = json.load(f)
response_obj['error']['code'] = 'Base.1.5.PropertyMissing'
response = mock.Mock(spec=['json', 'status_code'])
response.json.return_value = response_obj
error = exceptions.HTTPError('GET', 'VirtualMedia', response)
retval = self.sys_virtual_media.is_transfer_protocol_required(error)
self.assertTrue(retval)
def test_eject_media_none(self):

View File

@ -362,6 +362,7 @@ class ConnectorOpTestCase(base.TestCase):
self.assertEqual(http_client.BAD_REQUEST, exc.status_code)
self.assertIsNotNone(exc.body)
self.assertIn('body submitted was malformed JSON', exc.detail)
self.assertEqual(len(exc.extended_info), 3, exc.extended_info)
def test_known_http_error_nonlist_ext_info(self):
self.request.return_value.status_code =\
@ -377,6 +378,8 @@ class ConnectorOpTestCase(base.TestCase):
self.assertEqual(http_client.UNSUPPORTED_MEDIA_TYPE, exc.status_code)
self.assertIsNotNone(exc.body)
self.assertIn('See Resolution for information', exc.detail)
self.assertIn('unsupported media type',
exc.extended_info['Resolution'])
@mock.patch('time.sleep', autospec=True)
def test_not_found_error(self, mock_sleep):
@ -504,7 +507,7 @@ class ConnectorOpTestCase(base.TestCase):
'this is expected prior to authentication', 'HTTP GET '
'http://redfish/v1/SessionService returned code '
'%s. unknown error Extended information: '
'none' % http_client.FORBIDDEN)
'None' % http_client.FORBIDDEN)
self.assertEqual(http_client.FORBIDDEN, exc.status_code)
def test_blocking_no_location_header(self):
@ -557,7 +560,7 @@ class ConnectorOpTestCase(base.TestCase):
"authentication. %(err)s",
{'err': 'HTTP GET http://redfish/v1/SessionService '
'returned code HTTPStatus.FORBIDDEN. unknown error '
'Extended information: none'}
'Extended information: None'}
)
def test__op_raises_connection_error(self):