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
:members:
.. autoexception:: RemoteError

View File

@ -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

View File

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

View File

@ -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)

View File

@ -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)