]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add hook to translate exceptions into HTTP codes
authorKevin Benton <blak111@gmail.com>
Thu, 11 Jun 2015 09:28:33 +0000 (02:28 -0700)
committerKevin Benton <kevinbenton@buttewifi.com>
Sat, 1 Aug 2015 18:55:03 +0000 (18:55 +0000)
Adds a hook to catch neutron exceptions and translate them into
a corresponding HTTP error code. If no corresponding code is found,
an HTTP 500 is returned with the original exception logged so
internal state isn't leaked to the user.

Partially-Implements: blueprint wsgi-pecan-switch
Co-Authored-By: Brandon Logan <brandon.logan@rackspace.com>
Change-Id: Ia25733d6e394e034995890c806ed04e387d4ab58

neutron/newapi/app.py
neutron/newapi/controllers/root.py
neutron/newapi/hooks/__init__.py [new file with mode: 0644]
neutron/newapi/hooks/translation.py [new file with mode: 0644]
neutron/tests/functional/newapi/test_functional.py

index 448896cd9b60070a40b69a16909ed733becd1cd9..5eb294f85db33e1be2b524b726be2eca68291471 100644 (file)
@@ -19,6 +19,7 @@ from oslo_middleware import request_id
 import pecan
 
 from neutron.common import exceptions as n_exc
+from neutron.newapi import hooks
 from neutron.newapi import startup
 
 CONF = cfg.CONF
@@ -40,7 +41,9 @@ def setup_app(*args, **kwargs):
     }
     pecan_config = pecan.configuration.conf_from_dict(config)
 
-    app_hooks = []
+    app_hooks = [
+        hooks.ExceptionTranslationHook(),
+    ]
 
     app = pecan.make_app(
         pecan_config.app.root,
index f12ec2160b21cf977f0b829bcbe8c917d8314d32..a9ba2c62a49929732f010edcfa2a7b7c18fd85a3 100644 (file)
@@ -64,6 +64,9 @@ class GeneralController(object):
     def index(self):
         if pecan.request.method != 'GET':
             pecan.abort(405)
+        return self.get()
+
+    def get(self):
         return {'message': 'GET'}
 
     @when(index, method='PUT')
diff --git a/neutron/newapi/hooks/__init__.py b/neutron/newapi/hooks/__init__.py
new file mode 100644 (file)
index 0000000..4e2c75b
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright (c) 2015 Mirantis, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from neutron.newapi.hooks import translation
+
+
+ExceptionTranslationHook = translation.ExceptionTranslationHook
diff --git a/neutron/newapi/hooks/translation.py b/neutron/newapi/hooks/translation.py
new file mode 100644 (file)
index 0000000..bf39cd8
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright (c) 2015 Mirantis, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from pecan import hooks
+import webob.exc
+
+from neutron.api.v2 import base as v2base
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ExceptionTranslationHook(hooks.PecanHook):
+    def on_error(self, state, e):
+        # if it's already an http error, just return to let it go through
+        if isinstance(e, webob.exc.WSGIHTTPException):
+            return
+        for exc_class, to_class in v2base.FAULT_MAP.items():
+            if isinstance(e, exc_class):
+                raise to_class(e.message)
+        # leaked unexpected exception, convert to boring old 500 error and
+        # hide message from user in case it contained sensitive details
+        LOG.exception(_("An unexpected exception was caught: %s") % e)
+        raise webob.exc.HTTPInternalServerError(
+            _("An unexpected internal error occured."))
index 473a7b007b6bdefb5716c0c3e3ed7888a928bf65..9e43bcde1c783ebe93ec5551f3fd4c4f77e8a0ab 100644 (file)
@@ -15,6 +15,7 @@
 
 import os
 
+import mock
 from oslo_config import cfg
 from oslo_utils import uuidutils
 from pecan import set_config
@@ -112,3 +113,23 @@ class TestInvalidAuth(PecanFunctionalTest):
         cfg.CONF.set_override('auth_strategy', 'badvalue')
         with testtools.ExpectedException(n_exc.InvalidConfigurationOption):
             load_test_app(os.path.join(os.path.dirname(__file__), 'config.py'))
+
+
+class TestExceptionTranslationHook(PecanFunctionalTest):
+
+    def test_neutron_nonfound_to_webob_exception(self):
+        # this endpoint raises a Neutron notfound exception. make sure it gets
+        # translated into a 404 error
+        with mock.patch(
+                'neutron.newapi.controllers.root.GeneralController.get',
+                side_effect=n_exc.NotFound()):
+            response = self.app.get('/v2.0/ports.json', expect_errors=True)
+            self.assertEqual(response.status_int, 404)
+
+    def test_unexpected_exception(self):
+        with mock.patch(
+                'neutron.newapi.controllers.root.GeneralController.get',
+                side_effect=ValueError('secretpassword')):
+            response = self.app.get('/v2.0/ports.json', expect_errors=True)
+            self.assertNotIn(response.body, 'secretpassword')
+            self.assertEqual(response.status_int, 500)