
Now we have situation when openstack projects like Mistral needs extra oslo.messaging functionality. But it is too complicated now to to implement something new and integrate it with current code because there is a little bit mess. 1) Executor should be responsible for how to run jobs (but now also has code with server logic) 2) Dispatcher should be responsible for routing message to the target endpoint for processing (but it also has serialisation, sending replies, executing some executor's callbacks etc) 3) Server should do all server specific logic, we need to have different implementation of servers for RPC and notification, not different implementations of dispatchers This patch fixes 1-st point Change-Id: Ib6408f408889bb7b7056722be636a5547b1a780d
259 lines
9.2 KiB
Python
259 lines
9.2 KiB
Python
# Copyright 2011 Cloudscaling Group, Inc
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import os
|
|
import socket
|
|
import threading
|
|
|
|
from oslo_config import cfg
|
|
from stevedore import driver
|
|
|
|
from oslo_messaging._drivers import base
|
|
from oslo_messaging._drivers import common as rpc_common
|
|
from oslo_messaging._drivers.zmq_driver.client import zmq_client
|
|
from oslo_messaging._drivers.zmq_driver.server import zmq_server
|
|
from oslo_messaging._drivers.zmq_driver import zmq_async
|
|
from oslo_messaging._i18n import _LE
|
|
from oslo_messaging import server
|
|
|
|
|
|
RPCException = rpc_common.RPCException
|
|
|
|
zmq_opts = [
|
|
cfg.StrOpt('rpc_zmq_bind_address', default='*',
|
|
help='ZeroMQ bind address. Should be a wildcard (*), '
|
|
'an ethernet interface, or IP. '
|
|
'The "host" option should point or resolve to this '
|
|
'address.'),
|
|
|
|
cfg.StrOpt('rpc_zmq_matchmaker', default='redis',
|
|
help='MatchMaker driver.'),
|
|
|
|
cfg.StrOpt('rpc_zmq_concurrency', default='eventlet',
|
|
help='Type of concurrency used. Either "native" or "eventlet"'),
|
|
|
|
cfg.IntOpt('rpc_zmq_contexts', default=1,
|
|
help='Number of ZeroMQ contexts, defaults to 1.'),
|
|
|
|
cfg.IntOpt('rpc_zmq_topic_backlog',
|
|
help='Maximum number of ingress messages to locally buffer '
|
|
'per topic. Default is unlimited.'),
|
|
|
|
cfg.StrOpt('rpc_zmq_ipc_dir', default='/var/run/openstack',
|
|
help='Directory for holding IPC sockets.'),
|
|
|
|
cfg.StrOpt('rpc_zmq_host', default=socket.gethostname(),
|
|
sample_default='localhost',
|
|
help='Name of this node. Must be a valid hostname, FQDN, or '
|
|
'IP address. Must match "host" option, if running Nova.'),
|
|
|
|
cfg.IntOpt('rpc_cast_timeout', default=-1,
|
|
help='Seconds to wait before a cast expires (TTL). '
|
|
'The default value of -1 specifies an infinite linger '
|
|
'period. The value of 0 specifies no linger period. '
|
|
'Pending messages shall be discarded immediately '
|
|
'when the socket is closed. 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.'),
|
|
|
|
cfg.IntOpt('zmq_target_expire', default=120,
|
|
help='Expiration timeout in seconds of a name service record '
|
|
'about existing target ( < 0 means no timeout).'),
|
|
|
|
cfg.BoolOpt('use_pub_sub', default=True,
|
|
help='Use PUB/SUB pattern for fanout methods. '
|
|
'PUB/SUB always uses proxy.'),
|
|
|
|
cfg.PortOpt('rpc_zmq_min_port',
|
|
default=49152,
|
|
help='Minimal port number for random ports range.'),
|
|
|
|
cfg.IntOpt('rpc_zmq_max_port',
|
|
min=1,
|
|
max=65536,
|
|
default=65536,
|
|
help='Maximal port number for random ports range.'),
|
|
|
|
cfg.IntOpt('rpc_zmq_bind_port_retries',
|
|
default=100,
|
|
help='Number of retries to find free port number before '
|
|
'fail with ZMQBindError.')
|
|
]
|
|
|
|
|
|
class LazyDriverItem(object):
|
|
|
|
def __init__(self, item_cls, *args, **kwargs):
|
|
self._lock = threading.Lock()
|
|
self.item = None
|
|
self.item_class = item_cls
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.process_id = os.getpid()
|
|
|
|
def get(self):
|
|
# NOTE(ozamiatin): Lazy initialization.
|
|
# All init stuff moved closer to usage point - lazy init.
|
|
# Better design approach is to initialize in the driver's
|
|
# __init__, but 'fork' extensively used by services
|
|
# breaks all things.
|
|
|
|
if self.item is not None and os.getpid() == self.process_id:
|
|
return self.item
|
|
|
|
with self._lock:
|
|
if self.item is None or os.getpid() != self.process_id:
|
|
self.process_id = os.getpid()
|
|
self.item = self.item_class(*self.args, **self.kwargs)
|
|
return self.item
|
|
|
|
def cleanup(self):
|
|
if self.item:
|
|
self.item.cleanup()
|
|
|
|
|
|
class ZmqDriver(base.BaseDriver):
|
|
|
|
"""ZeroMQ Driver implementation.
|
|
|
|
Provides implementation of RPC and Notifier APIs by means
|
|
of ZeroMQ library.
|
|
|
|
See :doc:`zmq_driver` for details.
|
|
"""
|
|
|
|
def __init__(self, conf, url, default_exchange=None,
|
|
allowed_remote_exmods=None):
|
|
"""Construct ZeroMQ driver.
|
|
|
|
Initialize driver options.
|
|
|
|
Construct matchmaker - pluggable interface to targets management
|
|
Name Service
|
|
|
|
Construct client and server controllers
|
|
|
|
:param conf: oslo messaging configuration object
|
|
:type conf: oslo_config.CONF
|
|
:param url: transport URL
|
|
:type url: TransportUrl
|
|
:param default_exchange: Not used in zmq implementation
|
|
:type default_exchange: None
|
|
:param allowed_remote_exmods: remote exception passing options
|
|
:type allowed_remote_exmods: list
|
|
"""
|
|
zmq = zmq_async.import_zmq()
|
|
if zmq is None:
|
|
raise ImportError(_LE("ZeroMQ is not available!"))
|
|
|
|
conf.register_opts(zmq_opts)
|
|
conf.register_opts(server._pool_opts)
|
|
conf.register_opts(base.base_opts)
|
|
self.conf = conf
|
|
self.allowed_remote_exmods = allowed_remote_exmods
|
|
|
|
self.matchmaker = driver.DriverManager(
|
|
'oslo.messaging.zmq.matchmaker',
|
|
self.conf.rpc_zmq_matchmaker,
|
|
).driver(self.conf)
|
|
|
|
self.client = LazyDriverItem(
|
|
zmq_client.ZmqClient, self.conf, self.matchmaker,
|
|
self.allowed_remote_exmods)
|
|
|
|
self.notifier = LazyDriverItem(
|
|
zmq_client.ZmqClient, self.conf, self.matchmaker,
|
|
self.allowed_remote_exmods)
|
|
|
|
super(ZmqDriver, self).__init__(conf, url, default_exchange,
|
|
allowed_remote_exmods)
|
|
|
|
def send(self, target, ctxt, message, wait_for_reply=None, timeout=None,
|
|
retry=None):
|
|
"""Send RPC message to server
|
|
|
|
:param target: Message destination target
|
|
:type target: oslo_messaging.Target
|
|
:param ctxt: Message context
|
|
:type ctxt: dict
|
|
:param message: Message payload to pass
|
|
:type message: dict
|
|
:param wait_for_reply: Waiting for reply flag
|
|
:type wait_for_reply: bool
|
|
:param timeout: Reply waiting timeout in seconds
|
|
:type timeout: int
|
|
:param retry: an optional default connection retries configuration
|
|
None or -1 means to retry forever
|
|
0 means no retry
|
|
N means N retries
|
|
:type retry: int
|
|
"""
|
|
client = self.client.get()
|
|
if wait_for_reply:
|
|
return client.send_call(target, ctxt, message, timeout, retry)
|
|
elif target.fanout:
|
|
client.send_fanout(target, ctxt, message, retry)
|
|
else:
|
|
client.send_cast(target, ctxt, message, retry)
|
|
|
|
def send_notification(self, target, ctxt, message, version, retry=None):
|
|
"""Send notification to server
|
|
|
|
:param target: Message destination target
|
|
:type target: oslo_messaging.Target
|
|
:param ctxt: Message context
|
|
:type ctxt: dict
|
|
:param message: Message payload to pass
|
|
:type message: dict
|
|
:param version: Messaging API version
|
|
:type version: str
|
|
:param retry: an optional default connection retries configuration
|
|
None or -1 means to retry forever
|
|
0 means no retry
|
|
N means N retries
|
|
:type retry: int
|
|
"""
|
|
client = self.notifier.get()
|
|
client.send_notify(target, ctxt, message, version, retry)
|
|
|
|
def listen(self, target):
|
|
"""Listen to a specified target on a server side
|
|
|
|
:param target: Message destination target
|
|
:type target: oslo_messaging.Target
|
|
"""
|
|
server = zmq_server.ZmqServer(self, self.conf, self.matchmaker)
|
|
server.listen(target)
|
|
return server
|
|
|
|
def listen_for_notifications(self, targets_and_priorities, pool):
|
|
"""Listen to a specified list of targets on a server side
|
|
|
|
:param targets_and_priorities: List of pairs (target, priority)
|
|
:type targets_and_priorities: list
|
|
:param pool: Not used for zmq implementation
|
|
:type pool: object
|
|
"""
|
|
server = zmq_server.ZmqServer(self, self.conf, self.matchmaker)
|
|
server.listen_notification(targets_and_priorities)
|
|
return server
|
|
|
|
def cleanup(self):
|
|
"""Cleanup all driver's connections finally
|
|
"""
|
|
self.client.cleanup()
|
|
self.notifier.cleanup()
|