]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
bp/api-error-codes
authorSalvatore Orlando <salvatore.orlando@eu.citrix.com>
Tue, 17 Jan 2012 01:23:00 +0000 (01:23 +0000)
committerSalvatore Orlando <salvatore.orlando@eu.citrix.com>
Wed, 25 Jan 2012 01:28:30 +0000 (01:28 +0000)
Restructured API error codes for Quantum API v1.1
This changeset provides the following changes:
- Only standard HTTP errors for Quantum API v1.1
- Customized fault response body formatting according to API version
- Changes to unit tests to deal with version specific status codes

NOTE: Client side changes are not in this branch.
They should be implemented within bp/quantum-client-1.1.
NOTE-2: Fixing references to QuantumHTTPErrors in extensions framework

Change-Id: I56f50b5c59d73fd6f02905106f0c2a3c8341a741

quantum/api/api_common.py
quantum/api/faults.py
quantum/api/networks.py
quantum/tests/unit/_test_api.py
quantum/tests/unit/test_api.py
quantum/tests/unit/test_extensions.py
quantum/wsgi.py

index 7c9235116f23153b63ddf55f6c763a9d1ae33050..82adc09cbacff4ddbcdb248ab02f18043ca6be96 100644 (file)
@@ -59,6 +59,8 @@ def create_resource(version, controller_dict):
     metadata = controller_dict[version][1]
     # and the third element the xml namespace
     xmlns = controller_dict[version][2]
+    # and also the function for building the fault body
+    fault_body_function = faults.fault_body_function(version)
 
     headers_serializer = HeaderSerializer()
     xml_serializer = wsgi.XMLDictSerializer(metadata, xmlns)
@@ -79,21 +81,35 @@ def create_resource(version, controller_dict):
     serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer)
     deserializer = wsgi.RequestDeserializer(body_deserializers)
 
-    return wsgi.Resource(controller, deserializer, serializer)
+    return wsgi.Resource(controller,
+                         fault_body_function,
+                         deserializer,
+                         serializer)
 
 
 def APIFaultWrapper(errors=None):
 
+    quantum_error_dict = {
+        '1.0': faults.Quantum10HTTPError,
+        '1.1': faults.Quantum11HTTPError
+    }
+
     def wrapper(func, **kwargs):
 
         def the_func(*args, **kwargs):
             try:
+                # Grab API version from type of controller
+                controller = args[0]
+                version = controller.version
                 return func(*args, **kwargs)
             except Exception as e:
                 if errors is not None and type(e) in errors:
-                    raise faults.QuantumHTTPError(e)
+                    # Version-specific behaviour
+                    quantum_error_class = quantum_error_dict[version]
+                    raise quantum_error_class(e)
                 # otherwise just re-raise
                 raise
+
         the_func.__name__ = func.__name__
         return the_func
 
index 8ca15f30056dcedb15d1c270f25d1cb1ebbd2971..c759a69da1b82e3c422132368f1fd0f0b8ff5f54 100644 (file)
@@ -29,7 +29,60 @@ _ALREADYATTACHED_EXPL = 'The resource is already attached to another port'
 _NOTIMPLEMENTED_EXPL = 'Not implemented'
 
 
-class QuantumHTTPError(webob.exc.HTTPClientError):
+def fault_body_function_v10(wrapped_exc):
+    """ This function creates the contents of the body for a fault
+    response for Quantum API v1.0.
+
+    :param wrapped_exc: Exception thrown by the Quantum service
+    :type wrapped_exc: quantum.common.exceptions.QuantumException
+    :returns: response body contents and serialization metadata
+    :rtype: tuple
+    """
+    code = wrapped_exc.status_int
+    fault_name = hasattr(wrapped_exc, 'title') and \
+                 wrapped_exc.title or "quantumServiceFault"
+    fault_data = {
+        fault_name: {
+            'code': code,
+            'message': wrapped_exc.explanation,
+            'detail': str(wrapped_exc.detail)}}
+    metadata = {'attributes': {fault_name: ['code']}}
+    return fault_data, metadata
+
+
+def fault_body_function_v11(wrapped_exc):
+    """ This function creates the contents of the body for a fault
+    response for Quantum API v1.0.
+
+    :param wrapped_exc: Exception thrown by the Quantum service
+    :type wrapped_exc: quantum.common.exceptions.QuantumException
+    :returns: response body contents and serialization metadata
+    :rtype: tuple
+    """
+    fault_name = hasattr(wrapped_exc, 'type') and \
+                 wrapped_exc.type or "QuantumServiceFault"
+    # Ensure first letter is capital
+    fault_name = fault_name[0].upper() + fault_name[1:]
+    fault_data = {
+        'QuantumError': {
+            'type': fault_name,
+            'message': wrapped_exc.explanation,
+            'detail': str(wrapped_exc.detail)}}
+    # Metadata not required for v11
+    return fault_data, None
+
+
+def fault_body_function(version):
+    # dict mapping API version to functions for building the
+    # fault response body
+    fault_body_function_dict = {
+        '1.0': fault_body_function_v10,
+        '1.1': fault_body_function_v11
+    }
+    return fault_body_function_dict.get(version, None)
+
+
+class Quantum10HTTPError(webob.exc.HTTPClientError):
 
     _fault_dict = {
             exceptions.NetworkNotFound: {
@@ -76,3 +129,54 @@ class QuantumHTTPError(webob.exc.HTTPClientError):
             self.title = _fault_data['title']
             self.explanation = _fault_data['explanation']
         super(webob.exc.HTTPClientError, self).__init__(inner_exc)
+
+
+class Quantum11HTTPError(webob.exc.HTTPClientError):
+
+    _fault_dict = {
+            exceptions.NetworkNotFound: {
+                'code': webob.exc.HTTPNotFound.code,
+                'title': webob.exc.HTTPNotFound.title,
+                'type': 'NetworkNotFound',
+                'explanation': _NETNOTFOUND_EXPL
+            },
+            exceptions.NetworkInUse: {
+                'code': webob.exc.HTTPConflict.code,
+                'title': webob.exc.HTTPConflict.title,
+                'type': 'NetworkInUse',
+                'explanation': _NETINUSE_EXPL
+            },
+            exceptions.PortNotFound: {
+                'code': webob.exc.HTTPNotFound.code,
+                'title': webob.exc.HTTPNotFound.title,
+                'type': 'PortNotFound',
+                'explanation': _PORTNOTFOUND_EXPL
+            },
+            exceptions.StateInvalid: {
+                'code': webob.exc.HTTPBadRequest.code,
+                'title': webob.exc.HTTPBadRequest.title,
+                'type': 'RequestedStateInvalid',
+                'explanation': _STATEINVALID_EXPL
+            },
+            exceptions.PortInUse: {
+                'code': webob.exc.HTTPConflict.code,
+                'title': webob.exc.HTTPConflict.title,
+                'type': 'PortInUse',
+                'explanation': _PORTINUSE_EXPL
+            },
+            exceptions.AlreadyAttached: {
+                'code': webob.exc.HTTPConflict.code,
+                'title': webob.exc.HTTPConflict.title,
+                'type': 'AlreadyAttached',
+                'explanation': _ALREADYATTACHED_EXPL
+            }
+    }
+
+    def __init__(self, inner_exc):
+        _fault_data = self._fault_dict.get(type(inner_exc), None)
+        if _fault_data:
+            self.code = _fault_data['code']
+            self.title = _fault_data['title']
+            self.explanation = _fault_data['explanation']
+            self.type = _fault_data['type']
+        super(webob.exc.HTTPClientError, self).__init__(inner_exc)
index 50b1e0aa71da3542dd5787e1f49a10854f00a0a3..4229b6020cd3940875421ae405036c04a8366575 100644 (file)
@@ -76,16 +76,13 @@ class Controller(common.QuantumController):
         """ Returns a list of network ids """
         return self._items(request, tenant_id)
 
-    @common.APIFaultWrapper()
+    @common.APIFaultWrapper([exception.NetworkNotFound])
     def show(self, request, tenant_id, id):
         """ Returns network details for the given network id """
-        try:
-            return self._item(request, tenant_id, id,
-                              net_details=True, port_details=False)
-        except exception.NetworkNotFound as e:
-            raise faults.QuantumHTTPError(e)
+        return self._item(request, tenant_id, id,
+                          net_details=True, port_details=False)
 
-    @common.APIFaultWrapper()
+    @common.APIFaultWrapper([exception.NetworkNotFound])
     def detail(self, request, **kwargs):
         tenant_id = kwargs.get('tenant_id')
         network_id = kwargs.get('id')
index 629c246b44a174a3cd365447368d733b63d8c329..fa01fa137e17b5bf781cfa63c2a18c94f5cbfd00 100644 (file)
@@ -184,7 +184,8 @@ class AbstractAPITest(unittest.TestCase):
                                                         "A_BAD_ID",
                                                         fmt)
         show_network_res = show_network_req.get_response(self.api)
-        self.assertEqual(show_network_res.status_int, 420)
+        self.assertEqual(show_network_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_show_network_not_found - fmt:%s - END", fmt)
 
     def _test_rename_network(self, fmt):
@@ -232,7 +233,8 @@ class AbstractAPITest(unittest.TestCase):
                                                             new_name,
                                                             fmt)
         update_network_res = update_network_req.get_response(self.api)
-        self.assertEqual(update_network_res.status_int, 420)
+        self.assertEqual(update_network_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_rename_network_not_found - fmt:%s - END",
                   fmt)
 
@@ -279,7 +281,8 @@ class AbstractAPITest(unittest.TestCase):
                                                             network_id,
                                                             fmt)
         delete_network_res = delete_network_req.get_response(self.api)
-        self.assertEqual(delete_network_res.status_int, 421)
+        self.assertEqual(delete_network_res.status_int,
+                         self._network_in_use_code)
         LOG.debug("_test_delete_network_in_use - fmt:%s - END", fmt)
 
     def _test_delete_network_with_unattached_port(self, fmt):
@@ -324,7 +327,8 @@ class AbstractAPITest(unittest.TestCase):
         list_port_req = testlib.port_list_request(self.tenant_id,
                                                   "A_BAD_ID", fmt)
         list_port_res = list_port_req.get_response(self.api)
-        self.assertEqual(list_port_res.status_int, 420)
+        self.assertEqual(list_port_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_list_ports_networknotfound - fmt:%s - END", fmt)
 
     def _test_list_ports_detail(self, fmt):
@@ -413,7 +417,8 @@ class AbstractAPITest(unittest.TestCase):
                                                         "A_BAD_ID", port_id,
                                                         fmt)
         show_port_res = show_port_req.get_response(self.api)
-        self.assertEqual(show_port_res.status_int, 420)
+        self.assertEqual(show_port_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_show_port_networknotfound - fmt:%s - END",
                   fmt)
 
@@ -425,7 +430,8 @@ class AbstractAPITest(unittest.TestCase):
                                                         "A_BAD_ID",
                                                         fmt)
         show_port_res = show_port_req.get_response(self.api)
-        self.assertEqual(show_port_res.status_int, 430)
+        self.assertEqual(show_port_res.status_int,
+                         self._port_not_found_code)
         LOG.debug("_test_show_port_portnotfound - fmt:%s - END", fmt)
 
     def _test_create_port_noreqbody(self, fmt):
@@ -463,7 +469,7 @@ class AbstractAPITest(unittest.TestCase):
                   fmt)
         port_state = "ACTIVE"
         self._create_port("A_BAD_ID", port_state, fmt,
-                          expected_res_status=420)
+                          expected_res_status=self._network_not_found_code)
         LOG.debug("_test_create_port_networknotfound - fmt:%s - END",
                   fmt)
 
@@ -520,7 +526,8 @@ class AbstractAPITest(unittest.TestCase):
                                                       network_id, port_id,
                                                       fmt)
         delete_port_res = delete_port_req.get_response(self.api)
-        self.assertEqual(delete_port_res.status_int, 432)
+        self.assertEqual(delete_port_res.status_int,
+                         self._port_in_use_code)
         LOG.debug("_test_delete_port_in_use - fmt:%s - END", fmt)
 
     def _test_delete_port_with_bad_id(self, fmt):
@@ -534,7 +541,8 @@ class AbstractAPITest(unittest.TestCase):
                                                       network_id, "A_BAD_ID",
                                                       fmt)
         delete_port_res = delete_port_req.get_response(self.api)
-        self.assertEqual(delete_port_res.status_int, 430)
+        self.assertEqual(delete_port_res.status_int,
+                         self._port_not_found_code)
         LOG.debug("_test_delete_port_with_bad_id - fmt:%s - END", fmt)
 
     def _test_delete_port_networknotfound(self, fmt):
@@ -547,7 +555,8 @@ class AbstractAPITest(unittest.TestCase):
                                                       "A_BAD_ID", port_id,
                                                       fmt)
         delete_port_res = delete_port_req.get_response(self.api)
-        self.assertEqual(delete_port_res.status_int, 420)
+        self.assertEqual(delete_port_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_delete_port_networknotfound - fmt:%s - END",
                   fmt)
 
@@ -603,7 +612,8 @@ class AbstractAPITest(unittest.TestCase):
                                                         new_port_state,
                                                         fmt)
         update_port_res = update_port_req.get_response(self.api)
-        self.assertEqual(update_port_res.status_int, 420)
+        self.assertEqual(update_port_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_set_port_state_networknotfound - fmt:%s - END",
                   fmt)
 
@@ -620,7 +630,8 @@ class AbstractAPITest(unittest.TestCase):
                                                         new_port_state,
                                                         fmt)
         update_port_res = update_port_req.get_response(self.api)
-        self.assertEqual(update_port_res.status_int, 430)
+        self.assertEqual(update_port_res.status_int,
+                         self._port_not_found_code)
         LOG.debug("_test_set_port_state_portnotfound - fmt:%s - END",
                   fmt)
 
@@ -636,7 +647,8 @@ class AbstractAPITest(unittest.TestCase):
                                                         new_port_state,
                                                         fmt)
         update_port_res = update_port_req.get_response(self.api)
-        self.assertEqual(update_port_res.status_int, 431)
+        self.assertEqual(update_port_res.status_int,
+                         self._port_state_invalid_code)
         LOG.debug("_test_set_port_state_stateinvalid - fmt:%s - END",
                   fmt)
 
@@ -691,7 +703,8 @@ class AbstractAPITest(unittest.TestCase):
                                                             port_id,
                                                             fmt)
         get_attachment_res = get_attachment_req.get_response(self.api)
-        self.assertEqual(get_attachment_res.status_int, 420)
+        self.assertEqual(get_attachment_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_show_attachment_networknotfound - fmt:%s - END",
                   fmt)
 
@@ -706,7 +719,8 @@ class AbstractAPITest(unittest.TestCase):
                                                             "A_BAD_ID",
                                                             fmt)
         get_attachment_res = get_attachment_req.get_response(self.api)
-        self.assertEqual(get_attachment_res.status_int, 430)
+        self.assertEqual(get_attachment_res.status_int,
+                         self._port_not_found_code)
         LOG.debug("_test_show_attachment_portnotfound - fmt:%s - END",
                   fmt)
 
@@ -738,7 +752,8 @@ class AbstractAPITest(unittest.TestCase):
                                                             interface_id,
                                                             fmt)
         put_attachment_res = put_attachment_req.get_response(self.api)
-        self.assertEqual(put_attachment_res.status_int, 420)
+        self.assertEqual(put_attachment_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_put_attachment_networknotfound - fmt:%s - END",
                   fmt)
 
@@ -755,7 +770,8 @@ class AbstractAPITest(unittest.TestCase):
                                                             interface_id,
                                                             fmt)
         put_attachment_res = put_attachment_req.get_response(self.api)
-        self.assertEqual(put_attachment_res.status_int, 430)
+        self.assertEqual(put_attachment_res.status_int,
+                         self._port_not_found_code)
         LOG.debug("_test_put_attachment_portnotfound - fmt:%s - END",
                   fmt)
 
@@ -791,7 +807,8 @@ class AbstractAPITest(unittest.TestCase):
                                                                port_id,
                                                                fmt)
         del_attachment_res = del_attachment_req.get_response(self.api)
-        self.assertEqual(del_attachment_res.status_int, 420)
+        self.assertEqual(del_attachment_res.status_int,
+                         self._network_not_found_code)
         LOG.debug("_test_delete_attachment_networknotfound -" \
                   " fmt:%s - END", fmt)
 
@@ -806,7 +823,8 @@ class AbstractAPITest(unittest.TestCase):
                                                                "A_BAD_ID",
                                                                fmt)
         del_attachment_res = del_attachment_req.get_response(self.api)
-        self.assertEqual(del_attachment_res.status_int, 430)
+        self.assertEqual(del_attachment_res.status_int,
+                         self._port_not_found_code)
         LOG.debug("_test_delete_attachment_portnotfound - " \
                   "fmt:%s - END", fmt)
 
index 57c5b4cb44642c758d2cfb85281d964db333dfd9..57ed6503f60e942b54245b44255d275b01d05172 100644 (file)
@@ -16,6 +16,8 @@
 #    under the License.
 #    @author: Salvatore Orlando, Citrix Systems
 
+from webob import exc
+
 import quantum.api.attachments as atts
 import quantum.api.networks as nets
 import quantum.api.ports as ports
@@ -53,6 +55,12 @@ class APITestV10(test_api.AbstractAPITest):
              {test_api.NETS: nets.ControllerV10._serialization_metadata,
               test_api.PORTS: ports.ControllerV10._serialization_metadata,
               test_api.ATTS: atts.ControllerV10._serialization_metadata})
+        self._network_not_found_code = 420
+        self._network_in_use_code = 421
+        self._port_not_found_code = 430
+        self._port_state_invalid_code = 431
+        self._port_in_use_code = 432
+        self._already_attached_code = 440
 
 
 class APITestV11(test_api.AbstractAPITest):
@@ -93,3 +101,9 @@ class APITestV11(test_api.AbstractAPITest):
              {test_api.NETS: nets.ControllerV11._serialization_metadata,
               test_api.PORTS: ports.ControllerV11._serialization_metadata,
               test_api.ATTS: atts.ControllerV11._serialization_metadata})
+        self._network_not_found_code = exc.HTTPNotFound.code
+        self._network_in_use_code = exc.HTTPConflict.code
+        self._port_not_found_code = exc.HTTPNotFound.code
+        self._port_state_invalid_code = exc.HTTPBadRequest.code
+        self._port_in_use_code = exc.HTTPConflict.code
+        self._already_attached_code = exc.HTTPConflict.code
index 2eaaeb93823f8eed2bb30de28fc0073c86eb81f3..48730b184be5500cbb2cb6a60342b992f207ee93 100644 (file)
@@ -67,7 +67,7 @@ class ResourceExtensionTest(unittest.TestCase):
             return {'data': {'id': id}}
 
         def notimplemented_function(self, request, id):
-            return faults.QuantumHTTPError(
+            return faults.Quantum10HTTPError(
                 exceptions.NotImplementedError("notimplemented_function"))
 
         def custom_member_action(self, request, id):
index 2efbf56c296bb70ed0d0b41c42cdc1aa725966c1..dfd1bd7404ebdb4e9890d933043139888879e34f 100644 (file)
@@ -701,18 +701,23 @@ class Resource(Application):
 
     """
 
-    def __init__(self, controller, deserializer=None, serializer=None):
+    def __init__(self, controller, fault_body_function,
+                 deserializer=None, serializer=None):
         """
         :param controller: object that implement methods created by routes lib
         :param deserializer: object that can serialize the output of a
                              controller into a webob response
         :param serializer: object that can deserialize a webob request
                            into necessary pieces
+        :param fault_body_function: a function that will build the response
+                                    body for HTTP errors raised by operations
+                                    on this resource object
 
         """
         self.controller = controller
         self.deserializer = deserializer or RequestDeserializer()
         self.serializer = serializer or ResponseSerializer()
+        self._fault_body_function = fault_body_function
         # use serializer's xmlns for populating Fault generator xmlns
         xml_serializer = self.serializer.body_serializers['application/xml']
         if hasattr(xml_serializer, 'xmlns'):
@@ -742,7 +747,9 @@ class Resource(Application):
             action_result = self.dispatch(request, action, args)
         except webob.exc.HTTPException as ex:
             LOG.info(_("HTTP exception thrown: %s"), unicode(ex))
-            action_result = Fault(ex, self._xmlns)
+            action_result = Fault(ex,
+                                  self._xmlns,
+                                  self._fault_body_function)
 
         if isinstance(action_result, dict) or action_result is None:
             response = self.serializer.serialize(action_result,
@@ -776,29 +783,32 @@ class Resource(Application):
                          self._xmlns)
 
 
+def _default_body_function(wrapped_exc):
+    code = wrapped_exc.status_int
+    fault_data = {
+        'Error': {
+            'code': code,
+            'message': wrapped_exc.explanation}}
+    # 'code' is an attribute on the fault tag itself
+    metadata = {'attributes': {'Error': 'code'}}
+    return fault_data, metadata
+
+
 class Fault(webob.exc.HTTPException):
     """ Generates an HTTP response from a webob HTTP exception"""
 
-    def __init__(self, exception, xmlns=None):
+    def __init__(self, exception, xmlns=None, body_function=None):
         """Creates a Fault for the given webob.exc.exception."""
         self.wrapped_exc = exception
         self.status_int = self.wrapped_exc.status_int
         self._xmlns = xmlns
+        self._body_function = body_function or _default_body_function
 
     @webob.dec.wsgify(RequestClass=Request)
     def __call__(self, req):
         """Generate a WSGI response based on the exception passed to ctor."""
         # Replace the body with fault details.
-        code = self.wrapped_exc.status_int
-        fault_name = hasattr(self.wrapped_exc, 'title') and \
-                     self.wrapped_exc.title or "quantumServiceFault"
-        fault_data = {
-            fault_name: {
-                'code': code,
-                'message': self.wrapped_exc.explanation,
-                'detail': str(self.wrapped_exc.detail)}}
-        # 'code' is an attribute on the fault tag itself
-        metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
+        fault_data, metadata = self._body_function(self.wrapped_exc)
         xml_serializer = XMLDictSerializer(metadata, self._xmlns)
         content_type = req.best_match_content_type()
         serializer = {