Expose RemoteError exception in the public API

If a remote endpoint raises an exception which the client is not allowed
to (or cannot) deserialize, then RPCClient.call() raises a RemoteError
exception instead.

Make this exception type part of the public API.

Change-Id: I70be0ab7d40af3224d93d6bd0522c1a82f6303c3
This commit is contained in:
Mark McLoughlin 2013-08-07 12:23:52 +01:00
parent 9ac9f615b2
commit 66f597f30d
5 changed files with 47 additions and 23 deletions

View File

@ -6,3 +6,5 @@ RPC Client
.. autoclass:: RPCClient .. autoclass:: RPCClient
:members: :members:
.. autoexception:: RemoteError

View File

@ -23,6 +23,7 @@ import sys
import traceback import traceback
from oslo.config import cfg from oslo.config import cfg
from oslo import messaging
import six import six
from oslo.messaging.openstack.common import importutils from oslo.messaging.openstack.common import importutils
@ -341,7 +342,7 @@ def deserialize_remote_exception(conf, data):
# order to prevent arbitrary code execution. # order to prevent arbitrary code execution.
conf.register_opts(_exception_opts) conf.register_opts(_exception_opts)
if module not in conf.allowed_rpc_exception_modules: 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: try:
mod = importutils.import_module(module) mod = importutils.import_module(module)
@ -351,7 +352,7 @@ def deserialize_remote_exception(conf, data):
failure = klass(*failure.get('args', []), **failure.get('kwargs', {})) failure = klass(*failure.get('args', []), **failure.get('kwargs', {}))
except (AttributeError, TypeError, ImportError): except (AttributeError, TypeError, ImportError):
return RemoteError(name, failure.get('message'), trace) return messaging.RemoteError(name, failure.get('message'), trace)
ex_type = type(failure) ex_type = type(failure)
str_override = lambda self: message str_override = lambda self: message

View File

@ -21,6 +21,7 @@ __all__ = [
'RPCDispatcher', 'RPCDispatcher',
'RPCDispatcherError', 'RPCDispatcherError',
'RPCVersionCapError', 'RPCVersionCapError',
'RemoteError',
'UnsupportedVersion', 'UnsupportedVersion',
'expected_exceptions', 'expected_exceptions',
'get_rpc_server', 'get_rpc_server',

View File

@ -20,6 +20,7 @@ __all__ = [
'ClientSendError', 'ClientSendError',
'RPCClient', 'RPCClient',
'RPCVersionCapError', 'RPCVersionCapError',
'RemoteError',
] ]
import inspect import inspect
@ -41,6 +42,26 @@ _client_opts = [
_LOG = logging.getLogger(__name__) _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): class RPCVersionCapError(exceptions.MessagingException):
def __init__(self, version, version_cap): def __init__(self, version, version_cap):
@ -335,7 +356,7 @@ class RPCClient(object):
:type method: str :type method: str
:param kwargs: a dict of method arguments :param kwargs: a dict of method arguments
:param kwargs: dict :param kwargs: dict
:raises: MessagingTimeout :raises: MessagingTimeout, RemoteError
""" """
return self.prepare().call(ctxt, method, **kwargs) return self.prepare().call(ctxt, method, **kwargs)

View File

@ -17,6 +17,7 @@ import sys
import testscenarios import testscenarios
from oslo import messaging
from oslo.messaging._drivers import common as exceptions from oslo.messaging._drivers import common as exceptions
from oslo.messaging.openstack.common import jsonutils from oslo.messaging.openstack.common import jsonutils
from tests import utils as test_utils from tests import utils as test_utils
@ -160,7 +161,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
args=['test'], args=['test'],
kwargs={}, kwargs={},
str='test\ntraceback\ntraceback\n', str='test\ntraceback\ntraceback\n',
msg='test', message='test',
remote_name='Exception', remote_name='Exception',
remote_args=('test\ntraceback\ntraceback\n', ), remote_args=('test\ntraceback\ntraceback\n', ),
remote_kwargs={})), remote_kwargs={})),
@ -172,7 +173,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
args=[], args=[],
kwargs={}, kwargs={},
str='test\ntraceback\ntraceback\n', str='test\ntraceback\ntraceback\n',
msg='I am Nova', message='I am Nova',
remote_name='NovaStyleException_Remote', remote_name='NovaStyleException_Remote',
remote_args=('I am Nova', ), remote_args=('I am Nova', ),
remote_kwargs={})), remote_kwargs={})),
@ -184,7 +185,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
args=['testing'], args=['testing'],
kwargs={}, kwargs={},
str='test\ntraceback\ntraceback\n', str='test\ntraceback\ntraceback\n',
msg='testing', message='testing',
remote_name='NovaStyleException_Remote', remote_name='NovaStyleException_Remote',
remote_args=('testing', ), remote_args=('testing', ),
remote_kwargs={})), remote_kwargs={})),
@ -196,7 +197,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
args=[], args=[],
kwargs={'who': 'Oslo'}, kwargs={'who': 'Oslo'},
str='test\ntraceback\ntraceback\n', str='test\ntraceback\ntraceback\n',
msg='I am Oslo', message='I am Oslo',
remote_name='KwargsStyleException_Remote', remote_name='KwargsStyleException_Remote',
remote_args=('I am Oslo', ), remote_args=('I am Oslo', ),
remote_kwargs={})), remote_kwargs={})),
@ -204,7 +205,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
dict(allowed=[], dict(allowed=[],
clsname='Exception', clsname='Exception',
modname='exceptions', modname='exceptions',
cls=exceptions.RemoteError, cls=messaging.RemoteError,
args=[], args=[],
kwargs={}, kwargs={},
str=("Remote error: Exception test\n" str=("Remote error: Exception test\n"
@ -212,8 +213,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
msg=("Remote error: Exception test\n" msg=("Remote error: Exception test\n"
"[u'traceback\\ntraceback\\n']."), "[u'traceback\\ntraceback\\n']."),
remote_name='RemoteError', remote_name='RemoteError',
remote_args=("Remote error: Exception test\n" remote_args=(),
"[u'traceback\\ntraceback\\n'].", ),
remote_kwargs={'exc_type': 'Exception', remote_kwargs={'exc_type': 'Exception',
'value': 'test', 'value': 'test',
'traceback': 'traceback\ntraceback\n'})), 'traceback': 'traceback\ntraceback\n'})),
@ -221,7 +221,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
dict(allowed=['notexist'], dict(allowed=['notexist'],
clsname='Exception', clsname='Exception',
modname='notexist', modname='notexist',
cls=exceptions.RemoteError, cls=messaging.RemoteError,
args=[], args=[],
kwargs={}, kwargs={},
str=("Remote error: Exception test\n" str=("Remote error: Exception test\n"
@ -229,8 +229,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
msg=("Remote error: Exception test\n" msg=("Remote error: Exception test\n"
"[u'traceback\\ntraceback\\n']."), "[u'traceback\\ntraceback\\n']."),
remote_name='RemoteError', remote_name='RemoteError',
remote_args=("Remote error: Exception test\n" remote_args=(),
"[u'traceback\\ntraceback\\n'].", ),
remote_kwargs={'exc_type': 'Exception', remote_kwargs={'exc_type': 'Exception',
'value': 'test', 'value': 'test',
'traceback': 'traceback\ntraceback\n'})), 'traceback': 'traceback\ntraceback\n'})),
@ -238,7 +237,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
dict(allowed=['exceptions'], dict(allowed=['exceptions'],
clsname='FarcicalError', clsname='FarcicalError',
modname='exceptions', modname='exceptions',
cls=exceptions.RemoteError, cls=messaging.RemoteError,
args=[], args=[],
kwargs={}, kwargs={},
str=("Remote error: FarcicalError test\n" str=("Remote error: FarcicalError test\n"
@ -246,8 +245,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
msg=("Remote error: FarcicalError test\n" msg=("Remote error: FarcicalError test\n"
"[u'traceback\\ntraceback\\n']."), "[u'traceback\\ntraceback\\n']."),
remote_name='RemoteError', remote_name='RemoteError',
remote_args=("Remote error: FarcicalError test\n" remote_args=(),
"[u'traceback\\ntraceback\\n'].", ),
remote_kwargs={'exc_type': 'FarcicalError', remote_kwargs={'exc_type': 'FarcicalError',
'value': 'test', 'value': 'test',
'traceback': 'traceback\ntraceback\n'})), 'traceback': 'traceback\ntraceback\n'})),
@ -255,7 +253,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
dict(allowed=['exceptions'], dict(allowed=['exceptions'],
clsname='Exception', clsname='Exception',
modname='exceptions', modname='exceptions',
cls=exceptions.RemoteError, cls=messaging.RemoteError,
args=[], args=[],
kwargs={'foobar': 'blaa'}, kwargs={'foobar': 'blaa'},
str=("Remote error: Exception test\n" str=("Remote error: Exception test\n"
@ -263,8 +261,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
msg=("Remote error: Exception test\n" msg=("Remote error: Exception test\n"
"[u'traceback\\ntraceback\\n']."), "[u'traceback\\ntraceback\\n']."),
remote_name='RemoteError', remote_name='RemoteError',
remote_args=("Remote error: Exception test\n" remote_args=(),
"[u'traceback\\ntraceback\\n'].", ),
remote_kwargs={'exc_type': 'Exception', remote_kwargs={'exc_type': 'Exception',
'value': 'test', 'value': 'test',
'traceback': 'traceback\ntraceback\n'})), 'traceback': 'traceback\ntraceback\n'})),
@ -272,7 +269,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
dict(allowed=['exceptions'], dict(allowed=['exceptions'],
clsname='SystemExit', clsname='SystemExit',
modname='exceptions', modname='exceptions',
cls=exceptions.RemoteError, cls=messaging.RemoteError,
args=[], args=[],
kwargs={}, kwargs={},
str=("Remote error: SystemExit test\n" str=("Remote error: SystemExit test\n"
@ -280,8 +277,7 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
msg=("Remote error: SystemExit test\n" msg=("Remote error: SystemExit test\n"
"[u'traceback\\ntraceback\\n']."), "[u'traceback\\ntraceback\\n']."),
remote_name='RemoteError', remote_name='RemoteError',
remote_args=("Remote error: SystemExit test\n" remote_args=(),
"[u'traceback\\ntraceback\\n'].", ),
remote_kwargs={'exc_type': 'SystemExit', remote_kwargs={'exc_type': 'SystemExit',
'value': 'test', 'value': 'test',
'traceback': 'traceback\ntraceback\n'})), 'traceback': 'traceback\ntraceback\n'})),
@ -310,5 +306,8 @@ class DeserializeRemoteExceptionTestCase(test_utils.BaseTestCase):
self.assertIsInstance(ex, self.cls) self.assertIsInstance(ex, self.cls)
self.assertEqual(ex.__class__.__name__, self.remote_name) self.assertEqual(ex.__class__.__name__, self.remote_name)
self.assertEqual(str(ex), self.str) 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) self.assertEqual(ex.args, self.remote_args)