From 72c40a5bb7d6f659ca8ef8ed785f889383795462 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Thu, 11 Jun 2015 02:28:33 -0700 Subject: [PATCH] Add hook to translate exceptions into HTTP codes 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 Change-Id: Ia25733d6e394e034995890c806ed04e387d4ab58 --- neutron/newapi/app.py | 5 ++- neutron/newapi/controllers/root.py | 3 ++ neutron/newapi/hooks/__init__.py | 19 ++++++++++ neutron/newapi/hooks/translation.py | 38 +++++++++++++++++++ .../functional/newapi/test_functional.py | 21 ++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 neutron/newapi/hooks/__init__.py create mode 100644 neutron/newapi/hooks/translation.py diff --git a/neutron/newapi/app.py b/neutron/newapi/app.py index 448896cd9..5eb294f85 100644 --- a/neutron/newapi/app.py +++ b/neutron/newapi/app.py @@ -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, diff --git a/neutron/newapi/controllers/root.py b/neutron/newapi/controllers/root.py index f12ec2160..a9ba2c62a 100644 --- a/neutron/newapi/controllers/root.py +++ b/neutron/newapi/controllers/root.py @@ -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 index 000000000..4e2c75bd2 --- /dev/null +++ b/neutron/newapi/hooks/__init__.py @@ -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 index 000000000..bf39cd8b6 --- /dev/null +++ b/neutron/newapi/hooks/translation.py @@ -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.")) diff --git a/neutron/tests/functional/newapi/test_functional.py b/neutron/tests/functional/newapi/test_functional.py index 473a7b007..9e43bcde1 100644 --- a/neutron/tests/functional/newapi/test_functional.py +++ b/neutron/tests/functional/newapi/test_functional.py @@ -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) -- 2.45.2