Send HTTP exceptions in the format expected by neutronclient

Neutron client for the v2 API expects exceptions to have 'type',
'message' and 'detail' attributes. That is why they need to be
included in the body of HTTP Exceptions.

Change-Id: I70bd47977e42ad7bac760600329e9440452b74bc
Closes-Bug: 1355902
This commit is contained in:
Elena Ezhova 2014-08-12 19:43:09 +04:00
parent f907677f1d
commit ea3c43a402
2 changed files with 86 additions and 36 deletions

View File

@ -99,17 +99,16 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
else:
LOG.exception(_('%s failed'), action)
e = translate(e, language)
# following structure is expected by python-neutronclient
err_data = {'type': e.__class__.__name__,
'message': e, 'detail': ''}
body = serializer.serialize({'NeutronError': err_data})
body = serializer.serialize(
{'NeutronError': get_exception_data(e)})
kwargs = {'body': body, 'content_type': content_type}
raise mapped_exc(**kwargs)
except webob.exc.HTTPException as e:
type_, value, tb = sys.exc_info()
LOG.exception(_('%s failed'), action)
translate(e, language)
value.body = serializer.serialize({'NeutronError': e})
value.body = serializer.serialize(
{'NeutronError': get_exception_data(e)})
value.content_type = content_type
six.reraise(type_, value, tb)
except NotImplementedError as e:
@ -121,7 +120,7 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
# because a plugin does not implement a feature,
# returning 500 is definitely confusing.
body = serializer.serialize(
{'NotImplementedError': e.message})
{'NotImplementedError': get_exception_data(e)})
kwargs = {'body': body, 'content_type': content_type}
raise webob.exc.HTTPNotImplemented(**kwargs)
except Exception:
@ -131,7 +130,9 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
msg = _('Request Failed: internal server error while '
'processing your request.')
msg = translate(msg, language)
body = serializer.serialize({'NeutronError': msg})
body = serializer.serialize(
{'NeutronError': get_exception_data(
webob.exc.HTTPInternalServerError(msg))})
kwargs = {'body': body, 'content_type': content_type}
raise webob.exc.HTTPInternalServerError(**kwargs)
@ -148,6 +149,21 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
return resource
def get_exception_data(e):
"""Extract the information about an exception.
Neutron client for the v2 API expects exceptions to have 'type', 'message'
and 'detail' attributes.This information is extracted and converted into a
dictionary.
:param e: the exception to be reraised
:returns: a structured dict with the exception data
"""
err_data = {'type': e.__class__.__name__,
'message': e, 'detail': ''}
return err_data
def translate(translatable, locale):
"""Translates the object to the given locale.

View File

@ -122,6 +122,13 @@ class RequestTestCase(base.BaseTestCase):
class ResourceTestCase(base.BaseTestCase):
@staticmethod
def _get_deserializer(req_format):
if req_format == 'json':
return wsgi.JSONDeserializer()
else:
return wsgi.XMLDeserializer()
def test_unmapped_neutron_error_with_json(self):
msg = u'\u7f51\u7edc'
@ -260,47 +267,74 @@ class ResourceTestCase(base.BaseTestCase):
self.assertIn(msg_translation,
str(wsgi.JSONDeserializer().deserialize(res.body)))
def test_http_error(self):
@staticmethod
def _make_request_with_side_effect(side_effect, req_format=None):
controller = mock.MagicMock()
controller.test.side_effect = exc.HTTPGatewayTimeout()
controller.test.side_effect = side_effect
resource = webtest.TestApp(wsgi_resource.Resource(controller))
environ = {'wsgiorg.routing_args': (None, {'action': 'test'})}
routing_args = {'action': 'test'}
if req_format:
routing_args.update({'format': req_format})
environ = {'wsgiorg.routing_args': (None, routing_args)}
res = resource.get('', extra_environ=environ, expect_errors=True)
self.assertEqual(res.status_int, exc.HTTPGatewayTimeout.code)
return res
def test_http_error(self):
res = self._make_request_with_side_effect(exc.HTTPGatewayTimeout())
# verify that the exception structure is the one expected
# by the python-neutronclient
self.assertEqual(exc.HTTPGatewayTimeout().explanation,
res.json['NeutronError']['message'])
self.assertEqual('HTTPGatewayTimeout',
res.json['NeutronError']['type'])
self.assertEqual('', res.json['NeutronError']['detail'])
self.assertEqual(exc.HTTPGatewayTimeout.code, res.status_int)
def _test_unhandled_error(self, req_format='json'):
expected_res = {'body': {'NeutronError':
{'detail': '',
'message': _(
'Request Failed: internal server '
'error while processing your request.'),
'type': 'HTTPInternalServerError'}}}
res = self._make_request_with_side_effect(side_effect=Exception(),
req_format=req_format)
self.assertEqual(exc.HTTPInternalServerError.code,
res.status_int)
self.assertEqual(expected_res,
self._get_deserializer(
req_format).deserialize(res.body))
def test_unhandled_error_with_json(self):
expected_res = {'body': {'NeutronError':
_('Request Failed: internal server error '
'while processing your request.')}}
controller = mock.MagicMock()
controller.test.side_effect = Exception()
resource = webtest.TestApp(wsgi_resource.Resource(controller))
environ = {'wsgiorg.routing_args': (None, {'action': 'test',
'format': 'json'})}
res = resource.get('', extra_environ=environ, expect_errors=True)
self.assertEqual(res.status_int, exc.HTTPInternalServerError.code)
self.assertEqual(wsgi.JSONDeserializer().deserialize(res.body),
expected_res)
self._test_unhandled_error()
def test_unhandled_error_with_xml(self):
self._test_unhandled_error(req_format='xml')
def _test_not_implemented_error(self, req_format='json'):
expected_res = {'body': {'NeutronError':
_('Request Failed: internal server error '
'while processing your request.')}}
controller = mock.MagicMock()
controller.test.side_effect = Exception()
{'detail': '',
'message': _(
'The server has either erred or is '
'incapable of performing the requested '
'operation.'),
'type': 'HTTPNotImplemented'}}}
resource = webtest.TestApp(wsgi_resource.Resource(controller))
res = self._make_request_with_side_effect(exc.HTTPNotImplemented(),
req_format=req_format)
self.assertEqual(exc.HTTPNotImplemented.code, res.status_int)
self.assertEqual(expected_res,
self._get_deserializer(
req_format).deserialize(res.body))
environ = {'wsgiorg.routing_args': (None, {'action': 'test',
'format': 'xml'})}
res = resource.get('', extra_environ=environ, expect_errors=True)
self.assertEqual(res.status_int, exc.HTTPInternalServerError.code)
self.assertEqual(wsgi.XMLDeserializer().deserialize(res.body),
expected_res)
def test_not_implemented_error_with_json(self):
self._test_not_implemented_error()
def test_not_implemented_error_with_xml(self):
self._test_not_implemented_error(req_format='xml')
def test_status_200(self):
controller = mock.MagicMock()