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()