ZMQ: Allow to raise remote exception

This patch adds possibility to re-raise on client's side exception, that
was raised on server side - serialize it on server side, restore and
re-raise on client.
Allowed to pass `allowed_remote_exmods` parameter from impl_zmq to
CallRequest class

Functional test CallTestCase.test_exception() passes now, so added it to
tox.ini. Modified zmq_receiver to be able run functional tests.

Change-Id: Ic055f3574962f3e80a0528d5d99320386303634e
This commit is contained in:
Victor Sergeyev 2015-07-09 11:39:31 +03:00 committed by Oleksii Zamiatin
parent 7df65f2937
commit bcdc0e88ec
11 changed files with 43 additions and 17 deletions

View File

@ -24,6 +24,7 @@ import sys
from oslo_config import cfg
from oslo_messaging._drivers import impl_zmq
from oslo_messaging._drivers.zmq_driver.broker import zmq_broker
from oslo_messaging._executors import base # FIXME(markmc)
CONF = cfg.CONF
@ -35,6 +36,9 @@ def main():
CONF(sys.argv[1:], project='oslo')
logging.basicConfig(level=logging.DEBUG)
with contextlib.closing(impl_zmq.ZmqProxy(CONF)) as reactor:
reactor.consume_in_thread()
with contextlib.closing(zmq_broker.ZmqBroker(CONF)) as reactor:
reactor.start()
reactor.wait()
if __name__ == "__main__":
main()

View File

@ -71,6 +71,11 @@ zmq_opts = [
default=30,
help='Seconds to wait before a cast expires (TTL). '
'Only supported by impl_zmq.'),
cfg.IntOpt('rpc_poll_timeout',
default=1,
help='The default number of seconds that poll should wait. '
'Poll raises timeout exception when timeout expired.'),
]
@ -95,7 +100,8 @@ class ZmqDriver(base.BaseDriver):
def send(self, target, ctxt, message, wait_for_reply=None, timeout=None,
retry=None):
if self.client is None:
self.client = zmq_client.ZmqClient(self.conf, self.matchmaker)
self.client = zmq_client.ZmqClient(self.conf, self.matchmaker,
self._allowed_remote_exmods)
if wait_for_reply:
return self.client.call(target, ctxt, message, timeout, retry)
else:

View File

@ -104,6 +104,7 @@ class BaseTcpFrontend(object):
def receive_incoming(self):
message, socket = self.poller.poll(1)
LOG.info(_LI("Message %s received."), message)
return message

View File

@ -19,13 +19,10 @@ import eventlet
import six
from oslo_messaging._drivers import common as rpc_common
from oslo_messaging._drivers.zmq_driver import zmq_async
from oslo_messaging._drivers.zmq_driver import zmq_poller
LOG = logging.getLogger(__name__)
zmq = zmq_async.import_zmq()
class GreenPoller(zmq_poller.ZmqPoller):

View File

@ -14,6 +14,7 @@
import logging
from oslo_messaging._drivers import common as rpc_common
from oslo_messaging._drivers.zmq_driver.rpc.client.zmq_request import Request
from oslo_messaging._drivers.zmq_driver import zmq_async
from oslo_messaging._drivers.zmq_driver import zmq_serializer
@ -28,7 +29,8 @@ zmq = zmq_async.import_zmq()
class CallRequest(Request):
def __init__(self, conf, target, context, message, timeout=None,
retry=None):
retry=None, allowed_remote_exmods=None):
self.allowed_remote_exmods = allowed_remote_exmods or []
try:
self.zmq_context = zmq.Context()
socket = self.zmq_context.socket(zmq.REQ)
@ -44,9 +46,14 @@ class CallRequest(Request):
self.socket.connect(self.connect_address)
except zmq.ZMQError as e:
LOG.error(_LE("Error connecting to socket: %s") % str(e))
raise
def receive_reply(self):
# NOTE(ozamiatin): Check for retry here (no retries now)
self.socket.setsockopt(zmq.RCVTIMEO, self.timeout)
reply = self.socket.recv_json()
return reply[u'reply']
if reply['failure']:
raise rpc_common.deserialize_remote_exception(
reply['failure'], self.allowed_remote_exmods)
else:
return reply['reply']

View File

@ -19,14 +19,16 @@ from oslo_messaging._drivers.zmq_driver.rpc.client import zmq_cast_dealer
class ZmqClient(object):
def __init__(self, conf, matchmaker=None):
def __init__(self, conf, matchmaker=None, allowed_remote_exmods=None):
self.conf = conf
self.allowed_remote_exmods = allowed_remote_exmods or []
self.cast_publisher = zmq_cast_dealer.DealerCastPublisher(conf,
matchmaker)
def call(self, target, context, message, timeout=None, retry=None):
request = zmq_call_request.CallRequest(self.conf, target, context,
message, timeout, retry)
request = zmq_call_request.CallRequest(
self.conf, target, context, message, timeout, retry,
self.allowed_remote_exmods)
return request()
def cast(self, target, context, message, timeout=None, retry=None):

View File

@ -16,6 +16,7 @@
import logging
from oslo_messaging._drivers import base
from oslo_messaging._drivers import common as rpc_common
from oslo_messaging._drivers.zmq_driver.rpc.server import zmq_base_consumer
from oslo_messaging._drivers.zmq_driver import zmq_async
from oslo_messaging._drivers.zmq_driver import zmq_topic as topic_utils
@ -37,6 +38,9 @@ class ZmqIncomingRequest(base.IncomingMessage):
self.poller = poller
def reply(self, reply=None, failure=None, log_failure=True):
if failure is not None:
failure = rpc_common.serialize_remote_exception(failure,
log_failure)
message_reply = {u'reply': reply,
u'failure': failure,
u'log_failure': log_failure}

View File

@ -39,7 +39,7 @@ class ZmqServer(base.Listener):
self.context)
def poll(self, timeout=None):
incoming = self.poller.poll(timeout)
incoming = self.poller.poll(timeout or self.conf.rpc_poll_timeout)
return incoming[0]
def stop(self):

View File

@ -103,8 +103,7 @@ class CallTestCase(utils.SkipIfNoTransportURL):
group = self.useFixture(utils.RpcServerGroupFixture(self.url))
client = group.client(1)
client.add(increment=2)
f = lambda: client.subtract(increment=3)
self.assertThat(f, matchers.raises(ValueError))
self.assertRaises(ValueError, client.subtract, increment=3)
def test_timeout_with_concurrently_queues(self):
transport = self.useFixture(utils.TransportFixture(self.url))

View File

@ -125,8 +125,13 @@ class RpcServerGroupFixture(fixtures.Fixture):
# NOTE(sileht): topic and servier_name must be uniq
# to be able to run all tests in parallel
self.topic = topic or str(uuid.uuid4())
self.names = names or ["server_%i_%s" % (i, uuid.uuid4())
for i in range(3)]
if self.url.startswith('zmq'):
# NOTE(viktors): We need to pass correct hots name to the to
# get_tcp_.*() methods. Should we use nameserver here?
self.names = names or [cfg.CONF.rpc_zmq_host for i in range(3)]
else:
self.names = names or ["server_%i_%s" % (i, uuid.uuid4())
for i in range(3)]
self.exchange = exchange
self.targets = [self._target(server=n) for n in self.names]
self.use_fanout_ctrl = use_fanout_ctrl

View File

@ -41,7 +41,8 @@ setenv = TRANSPORT_URL=amqp://stackqpid:secretqpid@127.0.0.1:65123//
commands = {toxinidir}/setup-test-env-qpid.sh python setup.py testr --slowest --testr-args='oslo_messaging.tests.functional'
[testenv:py27-func-zeromq]
commands = {toxinidir}/setup-test-env-zmq.sh python setup.py testr --slowest --testr-args='oslo_messaging.tests.functional'
commands = {toxinidir}/setup-test-env-zmq.sh python -m testtools.run oslo_messaging.tests.functional.test_functional.CallTestCase.test_exception
# commands = {toxinidir}/setup-test-env-zmq.sh python setup.py testr --slowest --testr-args='oslo_messaging.tests.functional'
[flake8]
show-source = True