From ea3c43a4026e9cab8d96aae7d75bf43be40609b8 Mon Sep 17 00:00:00 2001 From: Elena Ezhova Date: Tue, 12 Aug 2014 19:43:09 +0400 Subject: [PATCH] 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 --- neutron/api/v2/resource.py | 30 +++++-- neutron/tests/unit/test_api_v2_resource.py | 92 +++++++++++++++------- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/neutron/api/v2/resource.py b/neutron/api/v2/resource.py index c76b9e6b224..d65a95d1c46 100644 --- a/neutron/api/v2/resource.py +++ b/neutron/api/v2/resource.py @@ -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. diff --git a/neutron/tests/unit/test_api_v2_resource.py b/neutron/tests/unit/test_api_v2_resource.py index 965b512077d..a14c5ce89d6 100644 --- a/neutron/tests/unit/test_api_v2_resource.py +++ b/neutron/tests/unit/test_api_v2_resource.py @@ -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()