summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 = {