diff --git a/doc/source/rpcclient.rst b/doc/source/rpcclient.rst index 80730f5a3..5baada56e 100644 --- a/doc/source/rpcclient.rst +++ b/doc/source/rpcclient.rst @@ -6,3 +6,5 @@ RPC Client .. autoclass:: RPCClient :members: + +.. autoexception:: RemoteError diff --git a/oslo/messaging/_drivers/common.py b/oslo/messaging/_drivers/common.py index 4e25d1f0f..b9b68d6e6 100644 --- a/oslo/messaging/_drivers/common.py +++ b/oslo/messaging/_drivers/common.py @@ -23,6 +23,7 @@ import sys import traceback from oslo.config import cfg +from oslo import messaging import six from oslo.messaging.openstack.common import importutils @@ -341,7 +342,7 @@ def deserialize_remote_exception(conf, data): # order to prevent arbitrary code execution. conf.register_opts(_exception_opts) if module not in conf.allowed_rpc_exception_modules: - return RemoteError(name, failure.get('message'), trace) + return messaging.RemoteError(name, failure.get('message'), trace) try: mod = importutils.import_module(module) @@ -351,7 +352,7 @@ def deserialize_remote_exception(conf, data): failure = klass(*failure.get('args', []), **failure.get('kwargs', {})) except (AttributeError, TypeError, ImportError): - return RemoteError(name, failure.get('message'), trace) + return messaging.RemoteError(name, failure.get('message'), trace) ex_type = type(failure) str_override = lambda self: message diff --git a/oslo/messaging/rpc/__init__.py b/oslo/messaging/rpc/__init__.py index 69d0ebd87..7f277d891 100644 --- a/oslo/messaging/rpc/__init__.py +++ b/oslo/messaging/rpc/__init__.py @@ -21,6 +21,7 @@ __all__ = [ 'RPCDispatcher', 'RPCDispatcherError', 'RPCVersionCapError', + 'RemoteError', 'UnsupportedVersion', 'expected_exceptions', 'get_rpc_server', diff --git a/oslo/messaging/rpc/client.py b/oslo/messaging/rpc/client.py index 616a6060e..55ba4819d 100644 --- a/oslo/messaging/rpc/client.py +++ b/oslo/messaging/rpc/client.py @@ -20,6 +20,7 @@ __all__ = [ 'ClientSendError', 'RPCClient', 'RPCVersionCapError', + 'RemoteError', ] import inspect @@ -41,6 +42,26 @@ _client_opts = [ _LOG = logging.getLogger(__name__) +class RemoteError(exceptions.MessagingException): + + """Signifies that a remote endpoint method has raised an exception. + + Contains a string representation of the type of the original exception, + the value of the original exception, and the traceback. These are + sent to the parent as a joined string so printing the exception + contains all of the relevant info. + """ + + def __init__(self, exc_type=None, value=None, traceback=None): + self.exc_type = exc_type + self.value = value + self.traceback = traceback + msg = ("Remote error: %(exc_type)s %(value)s\n%(traceback)s." % + dict(exc_type=self.exc_type, value=self.value, + traceback=self.traceback)) + super(RemoteError, self).__init__(msg) + + class RPCVersionCapError(exceptions.MessagingException): def __init__(self, version, version_cap): @@ -335,7 +356,7 @@ class RPCClient(object): :type method: str :param kwargs: a dict of method arguments :param kwargs: dict - :raises: MessagingTimeout + :raises: MessagingTimeout, RemoteError """ return self.prepare().call(ctxt, method, **kwargs) diff --git a/tests/test_exception_serialization.py b/tests/test_exception_serialization.py index a1de6eee6..71195b806 100644 --- a/tests/test_exception_serialization.py +++ b/tests/test_exception_serialization.py @@ -17,6 +17,7 @@ import sys import testscenarios +from oslo import messaging from oslo.messaging._drivers import common as exceptions from oslo.messaging.openstack.common import jsonutils from tests import utils as test_utils @@ -160,7 +161,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): args=['test'], kwargs={}, str='test\ntraceback\ntraceback\n', - msg='test', + message='test', remote_name='Exception', remote_args=('test\ntraceback\ntraceback\n', ), remote_kwargs={})), @@ -172,7 +173,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): args=[], kwargs={}, str='test\ntraceback\ntraceback\n', - msg='I am Nova', + message='I am Nova', remote_name='NovaStyleException_Remote', remote_args=('I am Nova', ), remote_kwargs={})), @@ -184,7 +185,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): args=['testing'], kwargs={}, str='test\ntraceback\ntraceback\n', - msg='testing', + message='testing', remote_name='NovaStyleException_Remote', remote_args=('testing', ), remote_kwargs={})), @@ -196,7 +197,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): args=[], kwargs={'who': 'Oslo'}, str='test\ntraceback\ntraceback\n', - msg='I am Oslo', + message='I am Oslo', remote_name='KwargsStyleException_Remote', remote_args=('I am Oslo', ), remote_kwargs={})), @@ -204,7 +205,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): dict(allowed=[], clsname='Exception', modname='exceptions', - cls=exceptions.RemoteError, + cls=messaging.RemoteError, args=[], kwargs={}, str=("Remote error: Exception test\n" @@ -212,8 +213,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): msg=("Remote error: Exception test\n" "[u'traceback\\ntraceback\\n']."), remote_name='RemoteError', - remote_args=("Remote error: Exception test\n" - "[u'traceback\\ntraceback\\n'].", ), + remote_args=(), remote_kwargs={'exc_type': 'Exception', 'value': 'test', 'traceback': 'traceback\ntraceback\n'})), @@ -221,7 +221,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): dict(allowed=['notexist'], clsname='Exception', modname='notexist', - cls=exceptions.RemoteError, + cls=messaging.RemoteError, args=[], kwargs={}, str=("Remote error: Exception test\n" @@ -229,8 +229,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): msg=("Remote error: Exception test\n" "[u'traceback\\ntraceback\\n']."), remote_name='RemoteError', - remote_args=("Remote error: Exception test\n" - "[u'traceback\\ntraceback\\n'].", ), + remote_args=(), remote_kwargs={'exc_type': 'Exception', 'value': 'test', 'traceback': 'traceback\ntraceback\n'})), @@ -238,7 +237,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): dict(allowed=['exceptions'], clsname='FarcicalError', modname='exceptions', - cls=exceptions.RemoteError, + cls=messaging.RemoteError, args=[], kwargs={}, str=("Remote error: FarcicalError test\n" @@ -246,8 +245,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): msg=("Remote error: FarcicalError test\n" "[u'traceback\\ntraceback\\n']."), remote_name='RemoteError', - remote_args=("Remote error: FarcicalError test\n" - "[u'traceback\\ntraceback\\n'].", ), + remote_args=(), remote_kwargs={'exc_type': 'FarcicalError', 'value': 'test', 'traceback': 'traceback\ntraceback\n'})), @@ -255,7 +253,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): dict(allowed=['exceptions'], clsname='Exception', modname='exceptions', - cls=exceptions.RemoteError, + cls=messaging.RemoteError, args=[], kwargs={'foobar': 'blaa'}, str=("Remote error: Exception test\n" @@ -263,8 +261,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): msg=("Remote error: Exception test\n" "[u'traceback\\ntraceback\\n']."), remote_name='RemoteError', - remote_args=("Remote error: Exception test\n" - "[u'traceback\\ntraceback\\n'].", ), + remote_args=(), remote_kwargs={'exc_type': 'Exception', 'value': 'test', 'traceback': 'traceback\ntraceback\n'})), @@ -272,7 +269,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): dict(allowed=['exceptions'], clsname='SystemExit', modname='exceptions', - cls=exceptions.RemoteError, + cls=messaging.RemoteError, args=[], kwargs={}, str=("Remote error: SystemExit test\n" @@ -280,8 +277,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): msg=("Remote error: SystemExit test\n" "[u'traceback\\ntraceback\\n']."), remote_name='RemoteError', - remote_args=("Remote error: SystemExit test\n" - "[u'traceback\\ntraceback\\n'].", ), + remote_args=(), remote_kwargs={'exc_type': 'SystemExit', 'value': 'test', 'traceback': 'traceback\ntraceback\n'})), @@ -310,5 +306,8 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase): self.assertIsInstance(ex, self.cls) self.assertEqual(ex.__class__.__name__, self.remote_name) self.assertEqual(str(ex), self.str) - self.assertEqual(ex.message, self.msg) + if hasattr(self, 'msg'): + self.assertEqual(ex.msg, self.msg) + else: + self.assertEqual(ex.message, self.message) self.assertEqual(ex.args, self.remote_args)