summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Sherborne <msherborne@gmail.com>2013-06-04 20:44:17 +1000
committerMatthew Sherborne <msherborne@gmail.com>2013-06-12 14:14:42 +1000
commit980fe5f790d000a44d4bd3ca066d2011be461a05 (patch)
tree742b6ac55905c296706e8484a7dd7b20cd02d419
parentda554770974706ffa6f69f12d9051062cd0d0b80 (diff)
downloadoslo-980fe5f790d000a44d4bd3ca066d2011be461a05.tar.gz
oslo-980fe5f790d000a44d4bd3ca066d2011be461a05.tar.xz
oslo-980fe5f790d000a44d4bd3ca066d2011be461a05.zip
Allow exceptions to hop up cells
When an exception happens in an RPC call using nova cells, it can travel back up several RPC boundaries. eg, child-cell=>parent-cell=>nova-api Before this patch if an exception (eg. AggregateNotFound) was raised in the bottom layer, the next layer up would turn it into a special exception ( AggregateNotFound_Remote ), then in the final layer, it would see this as an unrecognizable exception and raise RemoteException. After this patch, at each layer where the expeption is deserialized, it'll recognize exceptions with the _Remote postfix, and leave them as they are, instead of turning them into RemoteExceptions. It also preserves the exception's original __module__ now. Change-Id: I158a80f1cec20d3e1805b565ffddaffd7a15295b
-rw-r--r--openstack/common/rpc/common.py18
-rw-r--r--tests/unit/rpc/test_common.py40
2 files changed, 55 insertions, 3 deletions
diff --git a/openstack/common/rpc/common.py b/openstack/common/rpc/common.py
index f880608..5eacd32 100644
--- a/openstack/common/rpc/common.py
+++ b/openstack/common/rpc/common.py
@@ -70,6 +70,8 @@ _RPC_ENVELOPE_VERSION = '2.0'
_VERSION_KEY = 'oslo.version'
_MESSAGE_KEY = 'oslo.message'
+_REMOTE_POSTFIX = '_Remote'
+
class RPCException(Exception):
message = _("An unknown RPC related exception occurred.")
@@ -313,9 +315,18 @@ def serialize_remote_exception(failure_info, log_failure=True):
if hasattr(failure, 'kwargs'):
kwargs = failure.kwargs
+ # NOTE(matiu): With cells, it's possible to re-raise remote, remote
+ # exceptions. Lets turn it back into the original exception type.
+ cls_name = str(failure.__class__.__name__)
+ mod_name = str(failure.__class__.__module__)
+ if (cls_name.endswith(_REMOTE_POSTFIX) and
+ mod_name.endswith(_REMOTE_POSTFIX)):
+ cls_name = cls_name[:-len(_REMOTE_POSTFIX)]
+ mod_name = mod_name[:-len(_REMOTE_POSTFIX)]
+
data = {
- 'class': str(failure.__class__.__name__),
- 'module': str(failure.__class__.__module__),
+ 'class': cls_name,
+ 'module': mod_name,
'message': six.text_type(failure),
'tb': tb,
'args': failure.args,
@@ -352,8 +363,9 @@ def deserialize_remote_exception(conf, data):
ex_type = type(failure)
str_override = lambda self: message
- new_ex_type = type(ex_type.__name__ + "_Remote", (ex_type,),
+ new_ex_type = type(ex_type.__name__ + _REMOTE_POSTFIX, (ex_type,),
{'__str__': str_override, '__unicode__': str_override})
+ new_ex_type.__module__ = '%s%s' % (module, _REMOTE_POSTFIX)
try:
# NOTE(ameade): Dynamically create a new exception type and swap it in
# as the new type for the exception. This only works on user defined
diff --git a/tests/unit/rpc/test_common.py b/tests/unit/rpc/test_common.py
index 471048c..c2432f4 100644
--- a/tests/unit/rpc/test_common.py
+++ b/tests/unit/rpc/test_common.py
@@ -85,6 +85,41 @@ class RpcCommonTestCase(test_utils.BaseTestCase):
self.assertEqual(expected['module'], failure['module'])
self.assertEqual(expected['message'], failure['message'])
+ def test_serialize_remote_exception_cell_hop(self):
+ # A remote remote (no typo) exception should maintain its type and
+ # module, when being re-serialized, so that through any amount of cell
+ # hops up, it can pop out with the right type
+ expected = {
+ 'class': 'OpenstackException',
+ 'module': 'openstack.common.exception',
+ 'message': exception.OpenstackException.message,
+ 'tb': ['raise OpenstackException'],
+ }
+
+ def raise_remote_exception():
+ try:
+ raise exception.OpenstackException()
+ except Exception as e:
+ ex_type = type(e)
+ message = str(e)
+ str_override = lambda self: message
+ new_ex_type = type(ex_type.__name__ + "_Remote", (ex_type,),
+ {'__str__': str_override,
+ '__unicode__': str_override})
+ new_ex_type.__module__ = '%s_Remote' % e.__class__.__module__
+ e.__class__ = new_ex_type
+ raise e
+
+ try:
+ raise_remote_exception()
+ except Exception:
+ failure = rpc_common.serialize_remote_exception(sys.exc_info())
+
+ failure = jsonutils.loads(failure)
+ self.assertEqual(expected['class'], failure['class'])
+ self.assertEqual(expected['module'], failure['module'])
+ self.assertEqual(expected['message'], failure['message'])
+
def test_deserialize_remote_exception(self):
failure = {
'class': 'NotImplementedError',
@@ -114,6 +149,11 @@ class RpcCommonTestCase(test_utils.BaseTestCase):
self.assertTrue('An unknown' in six.text_type(after_exc))
#assure the traceback was added
self.assertTrue('raise OpenstackException' in six.text_type(after_exc))
+ self.assertEqual('OpenstackException_Remote',
+ after_exc.__class__.__name__)
+ self.assertEqual('openstack.common.exception_Remote',
+ after_exc.__class__.__module__)
+ self.assertTrue(isinstance(after_exc, exception.OpenstackException))
def test_deserialize_remote_exception_bad_module(self):
failure = {