]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Send HTTP exceptions in the format expected by neutronclient
authorElena Ezhova <eezhova@mirantis.com>
Tue, 12 Aug 2014 15:43:09 +0000 (19:43 +0400)
committerElena Ezhova <eezhova@mirantis.com>
Mon, 18 Aug 2014 13:21:45 +0000 (17:21 +0400)
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
neutron/tests/unit/test_api_v2_resource.py

index c76b9e6b224b8510868571a65d4b61282362c8e7..d65a95d1c46873c31446398692e0d5ddce80f91e 100644 (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.
 
index 965b512077db27487eb02d6f09d593e0a353eb9b..a14c5ce89d6e044cd98833edd6b37baf9e6cc034 100644 (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_unhandled_error_with_json(self):
+    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':
-                                 _('Request Failed: internal server error '
-                                   'while processing your request.')}}
-        controller = mock.MagicMock()
-        controller.test.side_effect = Exception()
+                                {'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))
 
-        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)
+    def test_unhandled_error_with_json(self):
+        self._test_unhandled_error()
 
     def test_unhandled_error_with_xml(self):
-        expected_res = {'body': {'NeutronError':
-                                 _('Request Failed: internal server error '
-                                   'while processing your request.')}}
-        controller = mock.MagicMock()
-        controller.test.side_effect = Exception()
+        self._test_unhandled_error(req_format='xml')
 
-        resource = webtest.TestApp(wsgi_resource.Resource(controller))
-
-        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(self, req_format='json'):
+        expected_res = {'body': {'NeutronError':
+                                {'detail': '',
+                                 'message': _(
+                                     'The server has either erred or is '
+                                     'incapable of performing the requested '
+                                     'operation.'),
+                                 'type': 'HTTPNotImplemented'}}}
+
+        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))
+
+    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()