FaultWrapper error mapping supports parent classes

Now the FaultWrapper middleware maps exceptions to webob ones using
a recursive method: if the exception itself is not in the error_map
dictionary, the method searches the first parent exception that has a
match in the same dictionary.
Without this change "any exception that subclasses an exception mapped
in FaultWrapper.error_map will raise an http 500 error instead of the
mapped http error. FaultWrapper should also map against all parent
types of an exception."
Two unit test were added: one to reproduce the bug and another to test
the fix when remote exceptions are handled.

Change-Id: I387e0acf35a4d6361494117218d5129f00d77eb7
Closes-Bug: #1232885
This commit is contained in:
Pablo Andres Fuente 2013-11-18 14:43:25 -03:00
parent 186b5a471b
commit 27e518c5c8
2 changed files with 66 additions and 2 deletions

View File

@ -79,6 +79,15 @@ class FaultWrapper(wsgi.Middleware):
'Invalid': webob.exc.HTTPBadRequest,
}
def _map_exception_to_error(self, class_exception):
if class_exception == Exception:
return webob.exc.HTTPInternalServerError
if class_exception.__name__ not in self.error_map:
return self._map_exception_to_error(class_exception.__base__)
return self.error_map[class_exception.__name__]
def _error(self, ex):
trace = None
@ -107,8 +116,7 @@ class FaultWrapper(wsgi.Middleware):
trace = msg_trace
if not webob_exc:
webob_exc = self.error_map.get(ex_type,
webob.exc.HTTPInternalServerError)
webob_exc = self._map_exception_to_error(ex.__class__)
error = {
'code': webob_exc.code,

View File

@ -20,6 +20,10 @@ from oslo.config import cfg
import heat.api.middleware.fault as fault
class StackNotFoundChild(heat_exc.StackNotFound):
pass
class FaultMiddlewareTest(HeatTestCase):
def test_openstack_exception_with_kwargs(self):
@ -85,3 +89,55 @@ class FaultMiddlewareTest(HeatTestCase):
'explanation': 'The resource could not be found.',
'title': 'Not Found'}
self.assertEqual(expected, msg)
def test_should_not_ignore_parent_classes(self):
wrapper = fault.FaultWrapper(None)
msg = wrapper._error(StackNotFoundChild(stack_name='a'))
expected = {'code': 404,
'error': {'message': 'The Stack (a) could not be found.',
'traceback': None,
'type': 'StackNotFoundChild'},
'explanation': 'The resource could not be found.',
'title': 'Not Found'}
self.assertEqual(expected, msg)
def test_internal_server_error_when_exeption_and_parents_not_mapped(self):
wrapper = fault.FaultWrapper(None)
class NotMappedException(Exception):
pass
msg = wrapper._error(NotMappedException('A message'))
expected = {'code': 500,
'error': {'message': u'A message',
'traceback': None,
'type': 'NotMappedException'},
'explanation': ('The server has either erred or is '
'incapable of performing the requested '
'operation.'),
'title': 'Internal Server Error'}
self.assertEqual(expected, msg)
def test_should_not_ignore_parent_classes_even_for_remote_ones(self):
# We want tracebacks
cfg.CONF.set_override('debug', True)
cfg.CONF.set_override('allowed_rpc_exception_modules',
['heat.tests.test_fault_middleware'])
error = StackNotFoundChild(stack_name='a')
exc_info = (type(error), error, None)
serialized = rpc_common.serialize_remote_exception(exc_info)
remote_error = rpc_common.deserialize_remote_exception(cfg.CONF,
serialized)
wrapper = fault.FaultWrapper(None)
msg = wrapper._error(remote_error)
expected_message, expected_traceback = str(remote_error).split('\n', 1)
expected = {'code': 404,
'error': {'message': expected_message,
'traceback': expected_traceback,
'type': 'StackNotFoundChild'},
'explanation': 'The resource could not be found.',
'title': 'Not Found'}
self.assertEqual(expected, msg)