From 6fceab1214e82303e50ae78c7512180915c0e6cf Mon Sep 17 00:00:00 2001
From: Davanum Srinivas <davanum@gmail.com>
Date: Mon, 19 Oct 2015 19:37:19 -0400
Subject: [PATCH 01/16] bootstrap branch

Change-Id: I22bb41192fa8d99dd47fcae49e6711cd1b5c84d7
---
 .gitreview | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitreview b/.gitreview
index beb811ae3..24d3e8df3 100644
--- a/.gitreview
+++ b/.gitreview
@@ -2,3 +2,4 @@
 host=review.openstack.org
 port=29418
 project=openstack/oslo.messaging.git
+branch=feature/pika

From ad2f475955ca5ef2bc750b2e6a610e6adfca928a Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Thu, 1 Oct 2015 18:53:17 +0300
Subject: [PATCH 02/16] Implements rabbit-pika driver

In this patch new driver implementation added and registered
in setup.cfg, integrated ith tox functional tests.

Implements: bp rabbit-pika

Depends-On: I7bda78820e657b1e97bf888d4065a917eb317cfb
Change-Id: I40842a03ce73d171644c362e3abfca2990aca58a
---
 oslo_messaging/_drivers/impl_pika.py | 1114 ++++++++++++++++++++++++++
 requirements.txt                     |    2 +
 setup-test-env-pika.sh               |   32 +
 setup.cfg                            |    1 +
 tox.ini                              |    3 +
 5 files changed, 1152 insertions(+)
 create mode 100644 oslo_messaging/_drivers/impl_pika.py
 create mode 100755 setup-test-env-pika.sh

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
new file mode 100644
index 000000000..84aa6882b
--- /dev/null
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -0,0 +1,1114 @@
+#    Copyright 2011 OpenStack Foundation
+#
+#    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 collections
+from concurrent import futures
+
+import pika
+from pika import adapters as pika_adapters
+from pika import credentials as pika_credentials
+from pika import exceptions as pika_exceptions
+from pika import spec as pika_spec
+
+import pika_pool
+import retrying
+
+import six
+import sys
+import threading
+import time
+import uuid
+
+
+from oslo_config import cfg
+from oslo_log import log as logging
+
+from oslo_messaging._drivers import common
+from oslo_messaging import exceptions
+
+from oslo_serialization import jsonutils
+
+
+LOG = logging.getLogger(__name__)
+
+pika_opts = [
+    cfg.IntOpt('channel_max', default=None,
+               help='Maximum number of channels to allow'),
+    cfg.IntOpt('frame_max', default=None,
+               help='The maximum byte size for an AMQP frame'),
+    cfg.IntOpt('heartbeat_interval', default=None,
+               help='How often to send heartbeats'),
+    cfg.BoolOpt('ssl', default=None,
+                help='Enable SSL'),
+    cfg.DictOpt('ssl_options', default=None,
+                help='Arguments passed to ssl.wrap_socket'),
+    cfg.FloatOpt('socket_timeout', default=None,
+                 help='Use for high latency networks'),
+]
+
+pika_pool_opts = [
+    cfg.IntOpt('pool_max_size', default=10,
+               help="Maximum number of connections to keep queued."),
+    cfg.IntOpt('pool_max_overflow', default=10,
+               help="Maximum number of connections to create above "
+                    "`pool_max_size`."),
+    cfg.IntOpt('pool_timeout', default=30,
+               help="Default number of seconds to wait for a connections to "
+                    "available"),
+    cfg.IntOpt('pool_recycle', default=None,
+               help="Lifetime of a connection (since creation) in seconds "
+                    "or None for no recycling. Expired connections are "
+                    "closed on acquire."),
+    cfg.IntOpt('pool_stale', default=None,
+               help="Threshold at which inactive (since release) connections "
+                    "are considered stale in seconds or None for no "
+                    "staleness. Stale connections are closed on acquire.")
+]
+
+notification_opts = [
+    cfg.BoolOpt('notification_persistence', default=False,
+                help="Persist notification messages."),
+    cfg.StrOpt('default_notification_exchange',
+               default="${control_exchange}_notification",
+               help="Exchange name for for sending notifications"),
+    cfg.IntOpt(
+        'default_notification_retry_attempts', default=-1,
+        help="Reconnecting retry count in case of connectivity problem during "
+             "sending notification, -1 means infinite retry."
+    ),
+    cfg.FloatOpt(
+        'notification_retry_delay', default=0.1,
+        help="Reconnecting retry delay in case of connectivity problem during "
+             "sending notification message"
+    )
+]
+
+rpc_opts = [
+    cfg.IntOpt('rpc_queue_expiration', default=60,
+               help="Time to live for rpc queues without consumers in "
+                    "seconds."),
+    cfg.StrOpt('default_rpc_exchange', default="${control_exchange}_rpc",
+               help="Exchange name for for sending RPC messages"),
+    cfg.StrOpt('rpc_reply_exchange', default="${control_exchange}_rpc_reply",
+               help="Exchange name for for receiving RPC replies"),
+    cfg.IntOpt(
+        'rpc_reply_retry_attempts', default=3,
+        help="Reconnecting retry count in case of connectivity problem during "
+             "sending reply. -1 means infinite retry."
+    ),
+    cfg.FloatOpt(
+        'rpc_reply_retry_delay', default=0.1,
+        help="Reconnecting retry delay in case of connectivity problem during "
+             "sending reply."
+    ),
+    cfg.IntOpt(
+        'default_rpc_retry_attempts', default=0,
+        help="Reconnecting retry count in case of connectivity problem during "
+             "sending RPC message, -1 means infinite retry."
+    ),
+    cfg.FloatOpt(
+        'rpc_retry_delay', default=0.1,
+        help="Reconnecting retry delay in case of connectivity problem during "
+             "sending RPC message"
+    )
+]
+
+
+def _is_eventlet_monkey_patched():
+    if 'eventlet.patcher' not in sys.modules:
+        return False
+    import eventlet.patcher
+    return eventlet.patcher.is_monkey_patched('thread')
+
+
+class ExchangeNotFoundException(exceptions.MessageDeliveryFailure):
+    pass
+
+
+class MessageRejectedException(exceptions.MessageDeliveryFailure):
+    pass
+
+
+class RoutingException(exceptions.MessageDeliveryFailure):
+    pass
+
+
+class ConnectionException(exceptions.MessagingException):
+    pass
+
+
+class EstablishConnectionException(ConnectionException):
+    pass
+
+
+class PooledConnectionWithConfirmations(pika_pool.Connection):
+    @property
+    def channel(self):
+        if self.fairy.channel is None:
+            self.fairy.channel = self.fairy.cxn.channel()
+            self.fairy.channel.confirm_delivery()
+        return self.fairy.channel
+
+
+class PikaEngine(object):
+    def __init__(self, conf, url, default_exchange=None):
+        self.conf = conf
+
+        self.default_rpc_exchange = (
+            conf.oslo_messaging_pika.default_rpc_exchange if
+            conf.oslo_messaging_pika.default_rpc_exchange else
+            default_exchange
+        )
+        self.rpc_reply_exchange = (
+            conf.oslo_messaging_pika.rpc_reply_exchange if
+            conf.oslo_messaging_pika.rpc_reply_exchange else
+            default_exchange
+        )
+
+        self.default_notification_exchange = (
+            conf.oslo_messaging_pika.default_notification_exchange if
+            conf.oslo_messaging_pika.default_notification_exchange else
+            default_exchange
+        )
+
+        self.notification_persistence = (
+            conf.oslo_messaging_pika.notification_persistence
+        )
+
+        self.rpc_reply_retry_attempts = (
+            conf.oslo_messaging_pika.rpc_reply_retry_attempts
+        )
+        if self.rpc_reply_retry_attempts is None:
+            raise ValueError("rpc_reply_retry_attempts should be integer")
+        self.rpc_reply_retry_delay = (
+            conf.oslo_messaging_pika.rpc_reply_retry_delay
+        )
+        if (self.rpc_reply_retry_delay is None or
+                self.rpc_reply_retry_delay < 0):
+            raise ValueError("rpc_reply_retry_delay should be non-negative "
+                             "integer")
+
+        self.default_rpc_retry_attempts = (
+            conf.oslo_messaging_pika.default_rpc_retry_attempts
+        )
+        if self.default_rpc_retry_attempts is None:
+            raise ValueError("default_rpc_retry_attempts should be an integer")
+        self.rpc_retry_delay = (
+            conf.oslo_messaging_pika.rpc_retry_delay
+        )
+        if (self.rpc_retry_delay is None or
+                self.rpc_retry_delay < 0):
+            raise ValueError("rpc_retry_delay should be non-negative integer")
+
+        self.default_notification_retry_attempts = (
+            conf.oslo_messaging_pika.default_notification_retry_attempts
+        )
+        if self.default_notification_retry_attempts is None:
+            raise ValueError("default_notification_retry_attempts should be "
+                             "an integer")
+        self.notification_retry_delay = (
+            conf.oslo_messaging_pika.notification_retry_delay
+        )
+        if (self.notification_retry_delay is None or
+                self.notification_retry_delay < 0):
+            raise ValueError("notification_retry_delay should be non-negative "
+                             "integer")
+
+        # preparing poller for listening replies
+        self._reply_queue = None
+
+        self._reply_listener = None
+        self._reply_waiting_future_list = []
+
+        self._reply_consumer_enabled = False
+        self._reply_consumer_thread_run_flag = True
+        self._reply_consumer_lock = threading.Lock()
+        self._puller_thread = None
+
+        # initializing connection parameters for configured RabbitMQ hosts
+        self._pika_next_connection_num = 0
+        common_pika_params = {
+            'virtual_host': url.virtual_host,
+            'channel_max': self.conf.oslo_messaging_pika.channel_max,
+            'frame_max': self.conf.oslo_messaging_pika.frame_max,
+            'heartbeat_interval':
+                self.conf.oslo_messaging_pika.heartbeat_interval,
+            'ssl': self.conf.oslo_messaging_pika.ssl,
+            'ssl_options': self.conf.oslo_messaging_pika.ssl_options,
+            'socket_timeout': self.conf.oslo_messaging_pika.socket_timeout,
+        }
+
+        self._pika_params_list = []
+        self._create_connection_lock = threading.Lock()
+
+        for transport_host in url.hosts:
+            pika_params = pika.ConnectionParameters(
+                host=transport_host.hostname,
+                port=transport_host.port,
+                credentials=pika_credentials.PlainCredentials(
+                    transport_host.username, transport_host.password
+                ),
+                **common_pika_params
+            )
+            self._pika_params_list.append(pika_params)
+
+        # initializing 2 connection pools: 1st for connections without
+        # confirmations, 2nd - with confirmations
+        self.connection_pool = pika_pool.QueuedPool(
+            create=self.create_connection,
+            max_size=self.conf.oslo_messaging_pika.pool_max_size,
+            max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
+            timeout=self.conf.oslo_messaging_pika.pool_timeout,
+            recycle=self.conf.oslo_messaging_pika.pool_recycle,
+            stale=self.conf.oslo_messaging_pika.pool_stale,
+        )
+
+        self.connection_with_confirmation_pool = pika_pool.QueuedPool(
+            create=self.create_connection,
+            max_size=self.conf.oslo_messaging_pika.pool_max_size,
+            max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
+            timeout=self.conf.oslo_messaging_pika.pool_timeout,
+            recycle=self.conf.oslo_messaging_pika.pool_recycle,
+            stale=self.conf.oslo_messaging_pika.pool_stale,
+        )
+
+        self.connection_with_confirmation_pool.Connection = (
+            PooledConnectionWithConfirmations
+        )
+
+    def create_connection(self):
+        """Create and return connection to any available host.
+
+        :return: cerated connection
+        :raise: ConnectionException if all hosts are not reachable
+        """
+        host_num = len(self._pika_params_list)
+        connection_attempts = host_num
+        while connection_attempts > 0:
+            with self._create_connection_lock:
+                try:
+                    return self.create_host_connection(
+                        self._pika_next_connection_num
+                    )
+                except pika_pool.Connection.connectivity_errors as e:
+                    LOG.warn(str(e))
+                    connection_attempts -= 1
+                    continue
+                finally:
+                    self._pika_next_connection_num += 1
+                    self._pika_next_connection_num %= host_num
+        raise EstablishConnectionException(
+            "Can not establish connection to any configured RabbitMQ host: " +
+            str(self._pika_params_list)
+        )
+
+    def create_host_connection(self, host_index):
+        """Create new connection to host #host_index
+
+        :return: New connection
+        """
+        return pika_adapters.BlockingConnection(
+            self._pika_params_list[host_index]
+        )
+
+    def declare_queue_binding(self, exchange, queue, routing_key,
+                              exchange_type, queue_expiration,
+                              queue_auto_delete, durable,
+                              timeout=None):
+        if timeout is not None and timeout < 0:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired."
+            )
+        try:
+            with self.connection_pool.acquire(timeout=timeout) as conn:
+                conn.channel.exchange_declare(
+                    exchange, exchange_type, auto_delete=True, durable=durable
+                )
+                arguments = {}
+
+                if queue_expiration > 0:
+                    arguments['x-expires'] = queue_expiration * 1000
+
+                conn.channel.queue_declare(
+                    queue, auto_delete=queue_auto_delete, durable=durable,
+                    arguments=arguments
+                )
+
+                conn.channel.queue_bind(queue, exchange, routing_key)
+        except pika_pool.Timeout as e:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired. {}.".format(str(e))
+            )
+        except pika_pool.Connection.connectivity_errors as e:
+            raise ConnectionException(
+                "Connectivity problem detected during declaring queue "
+                "binding: exchange:{}, queue: {}, routing_key: {}, "
+                "exchange_type: {}, queue_expiration: {}, queue_auto_delete: "
+                "{}, durable: {}. {}".format(
+                    exchange, queue, routing_key, exchange_type,
+                    queue_expiration, queue_auto_delete, durable, str(e)
+                )
+            )
+
+    @staticmethod
+    def _do_publish(pool, exchange, routing_key, body, properties,
+                    mandatory, expiration_time):
+        timeout = (None if expiration_time is None else
+                   expiration_time - time.time())
+        if timeout is not None and timeout < 0:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired."
+            )
+
+        try:
+            with pool.acquire(timeout=timeout) as conn:
+                if timeout is not None:
+                    properties.expiration = str(int(timeout))
+                conn.channel.publish(
+                    exchange=exchange,
+                    routing_key=routing_key,
+                    body=body,
+                    properties=properties,
+                    mandatory=mandatory
+                )
+        except pika_exceptions.NackError as e:
+            raise MessageRejectedException(
+                "Can not send message: [body: {}], properties: {}] to "
+                "target [exchange: {}, routing_key: {}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except pika_exceptions.UnroutableError as e:
+            raise RoutingException(
+                "Can not deliver message:[body:{}, properties: {}] to any"
+                "queue using target: [exchange:{}, "
+                "routing_key:{}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except pika_pool.Timeout as e:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired. {}".format(str(e))
+            )
+        except pika_pool.Connection.connectivity_errors as e:
+            if (isinstance(e, pika_exceptions.ChannelClosed)
+                    and e.args and e.args[0] == 404):
+                raise ExchangeNotFoundException(
+                    "Attempt to send message to not existing exchange "
+                    "detected, message: [body:{}, properties: {}], target: "
+                    "[exchange:{}, routing_key:{}]. {}".format(
+                        body, properties, exchange, routing_key, str(e)
+                    )
+                )
+            raise ConnectionException(
+                "Connectivity problem detected during sending the message: "
+                "[body:{}, properties: {}] to target: [exchange:{}, "
+                "routing_key:{}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+
+    def publish(self, exchange, routing_key, body, properties, confirm,
+                mandatory, expiration_time, retrier):
+        pool = (self.connection_with_confirmation_pool if confirm else
+                self.connection_pool)
+
+        LOG.debug(
+            "Sending message:[body:{}; properties: {}] to target: "
+            "[exchange:{}; routing_key:{}]".format(
+                body, properties, exchange, routing_key
+            )
+        )
+
+        do_publish = (self._do_publish if retrier is None else
+                      retrier(self._do_publish))
+
+        return do_publish(pool, exchange, routing_key, body, properties,
+                          mandatory, expiration_time)
+
+    def get_reply_q(self, timeout=None):
+        if self._reply_consumer_enabled:
+            return self._reply_queue
+
+        with self._reply_consumer_lock:
+            if self._reply_consumer_enabled:
+                return self._reply_queue
+
+            if self._reply_queue is None:
+                self._reply_queue = "reply.{}.{}.{}".format(
+                    self.conf.project, self.conf.prog, uuid.uuid4().hex
+                )
+
+            if self._reply_listener is None:
+                self._reply_listener = RpcReplyPikaListener(
+                    pika_engine=self,
+                    exchange=self.rpc_reply_exchange,
+                    queue=self._reply_queue,
+                )
+
+                self._reply_listener.start(timeout=timeout)
+
+            if self._puller_thread is None:
+                self._puller_thread = threading.Thread(target=self._poller)
+                self._puller_thread.daemon = True
+
+            if not self._puller_thread.is_alive():
+                self._puller_thread.start()
+
+            self._reply_consumer_enabled = True
+
+        return self._reply_queue
+
+    def _poller(self):
+        while self._reply_consumer_thread_run_flag:
+            try:
+                message = self._reply_listener.poll(timeout=1)
+                if message is None:
+                    continue
+                i = 0
+                curtime = time.time()
+                while (i < len(self._reply_waiting_future_list) and
+                        self._reply_consumer_thread_run_flag):
+                    msg_id, future, expiration = (
+                        self._reply_waiting_future_list[i]
+                    )
+                    if expiration and expiration < curtime:
+                        del self._reply_waiting_future_list[i]
+                    elif msg_id == message.msg_id:
+                        del self._reply_waiting_future_list[i]
+                        future.set_result(message)
+                    else:
+                        i += 1
+            except BaseException:
+                LOG.exception("Exception during reply polling")
+
+    def register_reply_waiter(self, msg_id, future, expiration_time):
+        self._reply_waiting_future_list.append(
+            (msg_id, future, expiration_time)
+        )
+
+    def cleanup(self):
+        with self._reply_consumer_lock:
+            self._reply_consumer_enabled = False
+
+            if self._puller_thread:
+                if self._puller_thread.is_alive():
+                    self._reply_consumer_thread_run_flag = False
+                    self._puller_thread.join()
+                self._puller_thread = None
+
+            if self._reply_listener:
+                self._reply_listener.stop()
+                self._reply_listener.cleanup()
+                self._reply_listener = None
+
+                self._reply_queue = None
+
+
+class PikaIncomingMessage(object):
+
+    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        self._pika_engine = pika_engine
+        self._no_ack = no_ack
+        self._channel = channel
+        self.delivery_tag = method.delivery_tag
+
+        self.content_type = getattr(properties, "content_type",
+                                    "application/json")
+        self.content_encoding = getattr(properties, "content_encoding",
+                                        "utf-8")
+
+        self.expiration = (
+            None if properties.expiration is None else
+            int(properties.expiration)
+        )
+
+        if self.content_type != "application/json":
+            raise NotImplementedError("Content-type['{}'] is not valid, "
+                                      "'application/json' only is supported.")
+
+        message_dict = common.deserialize_msg(
+            jsonutils.loads(body, encoding=self.content_encoding)
+        )
+
+        self.unique_id = message_dict.pop('_unique_id')
+        self.msg_id = message_dict.pop('_msg_id', None)
+        self.reply_q = message_dict.pop('_reply_q', None)
+
+        context_dict = {}
+
+        for key in list(message_dict.keys()):
+            key = six.text_type(key)
+            if key.startswith('_context_'):
+                value = message_dict.pop(key)
+                context_dict[key[9:]] = value
+
+        self.message = message_dict
+        self.ctxt = context_dict
+
+    def reply(self, reply=None, failure=None, log_failure=True):
+        if not (self.msg_id and self.reply_q):
+            return
+
+        if failure:
+            failure = common.serialize_remote_exception(failure, log_failure)
+
+        msg = {
+            'result': reply,
+            'failure': failure,
+            '_unique_id': uuid.uuid4().hex,
+            '_msg_id': self.msg_id,
+            'ending': True
+        }
+
+        def on_exception(ex):
+            if isinstance(ex, ConnectionException):
+                LOG.warn(str(ex))
+                return True
+            else:
+                return False
+
+        retrier = retrying.retry(
+            stop_max_attempt_number=(
+                None if self._pika_engine.rpc_reply_retry_attempts == -1
+                else self._pika_engine.rpc_reply_retry_attempts
+            ),
+            retry_on_exception=on_exception,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay,
+        )
+
+        try:
+            self._pika_engine.publish(
+                exchange=self._pika_engine.rpc_reply_exchange,
+                routing_key=self.reply_q,
+                body=jsonutils.dumps(
+                    common.serialize_msg(msg),
+                    encoding=self.content_encoding
+                ),
+                properties=pika_spec.BasicProperties(
+                    content_encoding=self.content_encoding,
+                    content_type=self.content_type,
+                ),
+                confirm=True,
+                mandatory=False,
+                expiration_time=time.time() + self.expiration,
+                retrier=retrier
+            )
+            LOG.debug(
+                "Message [id:'{}'] replied to '{}'.".format(
+                    self.msg_id, self.reply_q
+                )
+            )
+        except Exception:
+            LOG.exception(
+                "Message [id:'{}'] wasn't replied to : {}".format(
+                    self.msg_id, self.reply_q
+                )
+            )
+
+    def acknowledge(self):
+        if not self._no_ack:
+            try:
+                self._channel.basic_ack(delivery_tag=self.delivery_tag)
+            except Exception:
+                LOG.exception("Unable to acknowledge the message")
+
+    def requeue(self):
+        if not self._no_ack:
+            try:
+                return self._channel.basic_nack(delivery_tag=self.delivery_tag,
+                                                requeue=True)
+            except Exception:
+                LOG.exception("Unable to requeue the message")
+
+
+class PikaOutgoingMessage(object):
+
+    def __init__(self, pika_engine, message, context,
+                 content_type="application/json", content_encoding="utf-8"):
+        self._pika_engine = pika_engine
+
+        self.content_type = content_type
+        self.content_encoding = content_encoding
+
+        if self.content_type != "application/json":
+            raise NotImplementedError("Content-type['{}'] is not valid, "
+                                      "'application/json' only is supported.")
+
+        self.message = message
+        self.context = context
+
+        self.unique_id = uuid.uuid4().hex
+        self.msg_id = None
+
+    def send(self, exchange, routing_key='', confirm=True,
+             wait_for_reply=False, mandatory=True, persistent=False,
+             timeout=None, retrier=None):
+        msg = self.message.copy()
+
+        msg['_unique_id'] = self.unique_id
+
+        for key, value in self.context.iteritems():
+            key = six.text_type(key)
+            msg['_context_' + key] = value
+
+        properties = pika_spec.BasicProperties(
+            content_encoding=self.content_encoding,
+            content_type=self.content_type,
+            delivery_mode=2 if persistent else 1
+        )
+
+        expiration_time = (
+            None if timeout is None else timeout + time.time()
+        )
+
+        if wait_for_reply:
+            self.msg_id = uuid.uuid4().hex
+            msg['_msg_id'] = self.msg_id
+            LOG.debug('MSG_ID is %s', self.msg_id)
+
+            msg['_reply_q'] = self._pika_engine.get_reply_q(timeout)
+
+            future = futures.Future()
+
+            self._pika_engine.register_reply_waiter(
+                msg_id=self.msg_id, future=future,
+                expiration_time=expiration_time
+            )
+
+        self._pika_engine.publish(
+            exchange=exchange, routing_key=routing_key,
+            body=jsonutils.dumps(
+                common.serialize_msg(msg),
+                encoding=self.content_encoding
+            ),
+            properties=properties,
+            confirm=confirm,
+            mandatory=mandatory,
+            expiration_time=expiration_time,
+            retrier=retrier
+        )
+
+        if wait_for_reply:
+            try:
+                return future.result(timeout)
+            except futures.TimeoutError:
+                raise exceptions.MessagingTimeout()
+
+
+class PikaListener(object):
+    def __init__(self, pika_engine, no_ack, prefetch_count):
+        self._pika_engine = pika_engine
+
+        self._connection = None
+        self._channel = None
+        self._lock = threading.Lock()
+
+        self._prefetch_count = prefetch_count
+        self._no_ack = no_ack
+
+        self._started = False
+
+        self._message_queue = collections.deque()
+
+    def _reconnect(self):
+        self._connection = self._pika_engine.create_connection()
+        self._channel = self._connection.channel()
+        self._channel.basic_qos(prefetch_count=self._prefetch_count)
+
+        self._on_reconnected()
+
+    def _on_reconnected(self):
+        raise NotImplementedError(
+            "It is base class. Please declare consumers here"
+        )
+
+    def _start_consuming(self, queue):
+        self._channel.basic_consume(self._on_message_callback,
+                                    queue, no_ack=self._no_ack)
+
+    def _on_message_callback(self, unused, method, properties, body):
+        self._message_queue.append((self._channel, method, properties, body))
+
+    def _cleanup(self):
+        if self._channel:
+            try:
+                self._channel.close()
+            except Exception as ex:
+                if not pika_pool.Connection.is_connection_invalidated(ex):
+                    LOG.exception("Unexpected error during closing channel")
+            self._channel = None
+
+        if self._connection:
+            try:
+                self._connection.close()
+            except Exception as ex:
+                if not pika_pool.Connection.is_connection_invalidated(ex):
+                    LOG.exception("Unexpected error during closing connection")
+            self._connection = None
+
+    def poll(self, timeout=None):
+        start = time.time()
+        while not self._message_queue:
+            with self._lock:
+                if not self._started:
+                    return None
+                if self._channel is None:
+                    self._reconnect()
+                try:
+                    self._connection.process_data_events()
+                except pika_pool.Connection.connectivity_errors:
+                    self._cleanup()
+            if timeout and time.time() - start > timeout:
+                return None
+
+        return self._message_queue.popleft()
+
+    def start(self):
+        self._started = True
+
+    def stop(self):
+        with self._lock:
+            if not self._started:
+                return
+
+            self._started = False
+            self._cleanup()
+
+    def reconnect(self):
+        with self._lock:
+            self._cleanup()
+            try:
+                self._reconnect()
+            except Exception:
+                self._cleanup()
+                raise
+
+    def cleanup(self):
+        with self._lock:
+            self._cleanup()
+
+
+class RpcServicePikaListener(PikaListener):
+    def __init__(self, pika_engine, target, no_ack=True, prefetch_count=1):
+        self._target = target
+
+        super(RpcServicePikaListener, self).__init__(
+            pika_engine, no_ack=no_ack, prefetch_count=prefetch_count)
+
+    def _on_reconnected(self):
+        exchange = (self._target.exchange or
+                    self._pika_engine.default_rpc_exchange)
+        queue = '{}'.format(self._target.topic)
+        server_queue = '{}.{}'.format(queue, self._target.server)
+
+        fanout_exchange = '{}_fanout'.format(self._target.topic)
+
+        queue_expiration = (
+            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
+        self._pika_engine.declare_queue_binding(
+            exchange=exchange, queue=queue, routing_key=queue,
+            exchange_type='direct', queue_expiration=queue_expiration,
+            queue_auto_delete=False, durable=False
+        )
+        self._pika_engine.declare_queue_binding(
+            exchange=exchange, queue=server_queue, routing_key=server_queue,
+            exchange_type='direct', queue_expiration=queue_expiration,
+            queue_auto_delete=False, durable=False
+        )
+        self._pika_engine.declare_queue_binding(
+            exchange=fanout_exchange, queue=server_queue, routing_key="",
+            exchange_type='fanout', queue_expiration=queue_expiration,
+            queue_auto_delete=False, durable=False
+        )
+
+        self._start_consuming(queue)
+        self._start_consuming(server_queue)
+
+    def poll(self, timeout=None):
+        msg = super(RpcServicePikaListener, self).poll(timeout)
+        if msg is None:
+            return None
+        return PikaIncomingMessage(
+            self._pika_engine, *msg, no_ack=self._no_ack
+        )
+
+
+class RpcReplyPikaListener(PikaListener):
+    def __init__(self, pika_engine, exchange, queue, no_ack=True,
+                 prefetch_count=1):
+        self._exchange = exchange
+        self._queue = queue
+
+        super(RpcReplyPikaListener, self).__init__(
+            pika_engine, no_ack, prefetch_count
+        )
+
+    def _on_reconnected(self):
+        queue_expiration = (
+            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
+        self._pika_engine.declare_queue_binding(
+            exchange=self._exchange, queue=self._queue,
+            routing_key=self._queue, exchange_type='direct',
+            queue_expiration=queue_expiration, queue_auto_delete=False,
+            durable=False
+        )
+        self._start_consuming(self._queue)
+
+    def start(self, timeout=None):
+        super(RpcReplyPikaListener, self).start()
+
+        def on_exception(ex):
+            LOG.warn(str(ex))
+
+            return True
+
+        retrier = retrying.retry(
+            stop_max_attempt_number=self._pika_engine.rpc_reply_retry_attempts,
+            stop_max_delay=timeout,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay,
+            retry_on_exception=on_exception,
+        )
+
+        retrier(self.reconnect)()
+
+    def poll(self, timeout=None):
+        msg = super(RpcReplyPikaListener, self).poll(timeout)
+        if msg is None:
+            return None
+        return PikaIncomingMessage(
+            self._pika_engine, *msg, no_ack=self._no_ack
+        )
+
+
+class NotificationPikaListener(PikaListener):
+    def __init__(self, pika_engine, targets_and_priorities,
+                 queue_name=None, prefetch_count=100):
+        self._targets_and_priorities = targets_and_priorities
+        self._queue_name = queue_name
+
+        super(NotificationPikaListener, self).__init__(
+            pika_engine, no_ack=False, prefetch_count=prefetch_count
+        )
+
+    def _on_reconnected(self):
+        queues_to_consume = set()
+        for target, priority in self._targets_and_priorities:
+            routing_key = '%s.%s' % (target.topic, priority)
+            queue = self._queue_name or routing_key
+            self._pika_engine.declare_queue_binding(
+                exchange=(
+                    target.exchange or
+                    self._pika_engine.default_notification_exchange
+                ),
+                queue = queue,
+                routing_key=routing_key,
+                exchange_type='direct',
+                queue_expiration=None,
+                queue_auto_delete=False,
+                durable=self._pika_engine.notification_persistence,
+            )
+            queues_to_consume.add(queue)
+
+        for queue_to_consume in queues_to_consume:
+            self._start_consuming(queue_to_consume)
+
+    def poll(self, timeout=None):
+        msg = super(NotificationPikaListener, self).poll(timeout)
+        if msg is None:
+            return None
+        return PikaIncomingMessage(
+            self._pika_engine, *msg, no_ack=self._no_ack
+        )
+
+
+class PikaDriver(object):
+    def __init__(self, conf, url, default_exchange=None,
+                 allowed_remote_exmods=None):
+        if 'eventlet.patcher' in sys.modules:
+            import eventlet.patcher
+            if eventlet.patcher.is_monkey_patched('select'):
+                import select
+
+                try:
+                    del select.poll
+                except AttributeError:
+                    pass
+
+                try:
+                    del select.epoll
+                except AttributeError:
+                    pass
+
+        opt_group = cfg.OptGroup(name='oslo_messaging_pika',
+                                 title='Pika driver options')
+        conf.register_group(opt_group)
+        conf.register_opts(pika_opts, group=opt_group)
+        conf.register_opts(pika_pool_opts, group=opt_group)
+        conf.register_opts(rpc_opts, group=opt_group)
+        conf.register_opts(notification_opts, group=opt_group)
+
+        self.conf = conf
+        self._allowed_remote_exmods = allowed_remote_exmods
+
+        self._pika_engine = PikaEngine(conf, url, default_exchange)
+
+    def require_features(self, requeue=False):
+        pass
+
+    def send(self, target, ctxt, message, wait_for_reply=None, timeout=None,
+             retry=None):
+
+        if retry is None:
+            retry = self._pika_engine.default_rpc_retry_attempts
+
+        def on_exception(ex):
+            if isinstance(ex, (ConnectionException,
+                               exceptions.MessageDeliveryFailure)):
+                LOG.warn(str(ex))
+                return True
+            else:
+                return False
+
+        retrier = (
+            None if retry == 0 else
+            retrying.retry(
+                stop_max_attempt_number=(None if retry == -1 else retry),
+                retry_on_exception=on_exception,
+                wait_fixed=self._pika_engine.rpc_retry_delay,
+            )
+        )
+
+        msg = PikaOutgoingMessage(self._pika_engine, message, ctxt)
+
+        if target.fanout:
+            return msg.send(
+                exchange='{}_fanout'.format(target.topic),
+                timeout=timeout, confirm=True, mandatory=False,
+                retrier=retrier
+            )
+
+        queue = target.topic
+        if target.server:
+            queue = '{}.{}'.format(queue, target.server)
+
+        reply = msg.send(
+            exchange=target.exchange or self._pika_engine.default_rpc_exchange,
+            routing_key=queue,
+            wait_for_reply=wait_for_reply,
+            timeout=timeout,
+            confirm=True,
+            mandatory=True,
+            retrier=retrier
+        )
+
+        if reply is not None:
+            if reply.message['failure']:
+                ex = common.deserialize_remote_exception(
+                    reply.message['failure'], self._allowed_remote_exmods
+                )
+                raise ex
+
+            return reply.message['result']
+
+    def send_notification(self, target, ctxt, message, version, retry=None):
+        if retry is None:
+            retry = self._pika_engine.default_notification_retry_attempts
+
+        def on_exception(ex):
+            if isinstance(ex, (ExchangeNotFoundException, RoutingException)):
+                LOG.warn(str(ex))
+                try:
+                    self._pika_engine.declare_queue_binding(
+                        exchange=(
+                            target.exchange or
+                            self._pika_engine.default_notification_exchange
+                        ),
+                        queue=target.topic,
+                        routing_key=target.topic,
+                        exchange_type='direct',
+                        queue_expiration=False,
+                        queue_auto_delete=False,
+                        durable=self._pika_engine.notification_persistence,
+                    )
+                except ConnectionException as e:
+                    LOG.warn(str(e))
+                return True
+            elif isinstance(ex,
+                            (ConnectionException, MessageRejectedException)):
+                return True
+            else:
+                return False
+
+        retrier = retrying.retry(
+            stop_max_attempt_number=(None if retry == -1 else retry),
+            retry_on_exception=on_exception,
+            wait_fixed=self._pika_engine.notification_retry_delay,
+        )
+
+        msg = PikaOutgoingMessage(self._pika_engine, message, ctxt)
+
+        return msg.send(
+            exchange=(
+                target.exchange or
+                self._pika_engine.default_notification_exchange
+            ),
+            routing_key=target.topic,
+            wait_for_reply=False,
+            confirm=True,
+            mandatory=True,
+            persistent=self._pika_engine.notification_persistence,
+            retrier=retrier
+        )
+
+    def listen(self, target):
+        listener = RpcServicePikaListener(self._pika_engine, target)
+        listener.start()
+        return listener
+
+    def listen_for_notifications(self, targets_and_priorities, pool):
+        listener = NotificationPikaListener(self._pika_engine,
+                                            targets_and_priorities, pool)
+        listener.start()
+        return listener
+
+    def cleanup(self):
+        self._pika_engine.cleanup()
+
+
+class PikaDriverCompatibleWithRabbitDriver(PikaDriver):
+    """Old RabbitMQ driver creates exchange before sending message.
+    In this case if no rpc service listen this exchange message will be sent
+    to /dev/null but client will know anything about it. That is strange.
+    But for now we need to keep original behaviour
+    """
+    def send(self, target, ctxt, message, wait_for_reply=None, timeout=None,
+             retry=None):
+        try:
+            return super(PikaDriverCompatibleWithRabbitDriver, self).send(
+                target=target,
+                ctxt=ctxt,
+                message=message,
+                wait_for_reply=wait_for_reply,
+                timeout=timeout,
+                retry=retry
+            )
+        except exceptions.MessageDeliveryFailure:
+            if wait_for_reply:
+                raise exceptions.MessagingTimeout()
+            else:
+                return None
diff --git a/requirements.txt b/requirements.txt
index f00a9dc37..681d55ec9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -33,6 +33,8 @@ PyYAML>=3.1.0
 # we set the amqp version to ensure heartbeat works
 amqp>=1.4.0
 kombu>=3.0.7
+pika>=0.10.0
+pika-pool>=0.1.2
 
 # middleware
 oslo.middleware>=2.8.0 # Apache-2.0
diff --git a/setup-test-env-pika.sh b/setup-test-env-pika.sh
new file mode 100755
index 000000000..5fe189555
--- /dev/null
+++ b/setup-test-env-pika.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+set -e
+
+. tools/functions.sh
+
+DATADIR=$(mktemp -d /tmp/OSLOMSG-RABBIT.XXXXX)
+trap "clean_exit $DATADIR" EXIT
+
+export RABBITMQ_NODE_IP_ADDRESS=127.0.0.1
+export RABBITMQ_NODE_PORT=65123
+export RABBITMQ_NODENAME=oslomsg-test@localhost
+export RABBITMQ_LOG_BASE=$DATADIR
+export RABBITMQ_MNESIA_BASE=$DATADIR
+export RABBITMQ_PID_FILE=$DATADIR/pid
+export HOME=$DATADIR
+
+# NOTE(sileht): We directly use the rabbitmq scripts
+# to avoid distribution check, like running as root/rabbitmq
+# enforcing.
+export PATH=/usr/lib/rabbitmq/bin/:$PATH
+
+
+mkfifo ${DATADIR}/out
+rabbitmq-server &> ${DATADIR}/out &
+wait_for_line "Starting broker... completed" "ERROR:" ${DATADIR}/out
+
+rabbitmqctl add_user oslomsg oslosecret
+rabbitmqctl set_permissions "oslomsg" ".*" ".*" ".*"
+
+
+export TRANSPORT_URL=pika://oslomsg:oslosecret@127.0.0.1:65123//
+$*
diff --git a/setup.cfg b/setup.cfg
index ee63dc5bb..1524e0467 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -35,6 +35,7 @@ oslo.messaging.drivers =
 
     # This is just for internal testing
     fake = oslo_messaging._drivers.impl_fake:FakeDriver
+    pika = oslo_messaging._drivers.impl_pika:PikaDriverCompatibleWithRabbitDriver
 
 oslo.messaging.executors =
     aioeventlet = oslo_messaging._executors.impl_aioeventlet:AsyncioEventletExecutor
diff --git a/tox.ini b/tox.ini
index c576bed72..bb4a69a6a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -30,6 +30,9 @@ commands = {toxinidir}/setup-test-env-qpid.sh 0-10 python setup.py testr --slowe
 [testenv:py27-func-rabbit]
 commands = {toxinidir}/setup-test-env-rabbit.sh python setup.py testr --slowest --testr-args='oslo_messaging.tests.functional'
 
+[testenv:py27-func-pika]
+commands = {toxinidir}/setup-test-env-pika.sh python setup.py testr --slowest --testr-args='oslo_messaging.tests.functional'
+
 [testenv:py27-func-amqp1]
 setenv = TRANSPORT_URL=amqp://stackqpid:secretqpid@127.0.0.1:65123//
 # NOTE(flaper87): This gate job run on fedora21 for now.

From 9cae182b4939b5d7e267598327122bea89540384 Mon Sep 17 00:00:00 2001
From: dukhlov <dukhlov@mirantis.com>
Date: Sat, 24 Oct 2015 17:13:06 -0400
Subject: [PATCH 03/16] Fix fanout exchange name pattern

Change-Id: I0e3a85fe9bbd4a597b555302aa2c1c24045d2eec
---
 oslo_messaging/_drivers/impl_pika.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index 84aa6882b..8dfe3bda8 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -812,7 +812,9 @@ class RpcServicePikaListener(PikaListener):
         queue = '{}'.format(self._target.topic)
         server_queue = '{}.{}'.format(queue, self._target.server)
 
-        fanout_exchange = '{}_fanout'.format(self._target.topic)
+        fanout_exchange = '{}_fanout_{}'.format(
+            self._pika_engine.default_rpc_exchange, self._target.topic
+        )
 
         queue_expiration = (
             self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
@@ -997,7 +999,9 @@ class PikaDriver(object):
 
         if target.fanout:
             return msg.send(
-                exchange='{}_fanout'.format(target.topic),
+                exchange='{}_fanout_{}'.format(
+                    self._pika_engine.default_rpc_exchange, target.topic
+                ),
                 timeout=timeout, confirm=True, mandatory=False,
                 retrier=retrier
             )

From 968d3e6741ac95a0988ee5cbd2fee2ad53697d58 Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Mon, 2 Nov 2015 16:05:59 +0200
Subject: [PATCH 04/16] Fixes and improvements after testing on RabbitMQ
 cluster:

1) adds tcp_user_timeout parameter - timiout for unacked tcp pockets
2) adds host_connection_reconnect_delay parameter - delay for
reconnection to some host if error occurs during connection.
It allows to use other hosts if we have some host disconnected
3) adds rpc_listener_ack and rpc_listener_prefetch_count properties -
enable consumer acknowledges and set maximum number of unacknowledged
messages
4) fixes time units (in oslo.messaging it is seconds but in RabbitMQ -
milliseconds)

Change-Id: Ifd549a1eebeef27a3d36ceb6d3e8b1c76ea00b65
---
 oslo_messaging/_drivers/impl_pika.py | 344 +++++++++++++++++++--------
 1 file changed, 250 insertions(+), 94 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index 8dfe3bda8..fc6de2c82 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -24,6 +24,7 @@ import pika_pool
 import retrying
 
 import six
+import socket
 import sys
 import threading
 import time
@@ -46,30 +47,36 @@ pika_opts = [
                help='Maximum number of channels to allow'),
     cfg.IntOpt('frame_max', default=None,
                help='The maximum byte size for an AMQP frame'),
-    cfg.IntOpt('heartbeat_interval', default=None,
-               help='How often to send heartbeats'),
+    cfg.IntOpt('heartbeat_interval', default=1,
+               help="How often to send heartbeats for consumer's connections"),
     cfg.BoolOpt('ssl', default=None,
                 help='Enable SSL'),
     cfg.DictOpt('ssl_options', default=None,
                 help='Arguments passed to ssl.wrap_socket'),
-    cfg.FloatOpt('socket_timeout', default=None,
-                 help='Use for high latency networks'),
+    cfg.FloatOpt('socket_timeout', default=0.25,
+                 help="Set socket timeout in seconds for connection's socket"),
+    cfg.FloatOpt('tcp_user_timeout', default=0.25,
+                 help="Set TCP_USER_TIMEOUT in seconds for connection's "
+                      "socket"),
+    cfg.FloatOpt('host_connection_reconnect_delay', default=5,
+                 help="Set delay for reconnection to some host which has "
+                      "connection error")
 ]
 
 pika_pool_opts = [
     cfg.IntOpt('pool_max_size', default=10,
                help="Maximum number of connections to keep queued."),
-    cfg.IntOpt('pool_max_overflow', default=10,
+    cfg.IntOpt('pool_max_overflow', default=0,
                help="Maximum number of connections to create above "
                     "`pool_max_size`."),
     cfg.IntOpt('pool_timeout', default=30,
                help="Default number of seconds to wait for a connections to "
                     "available"),
-    cfg.IntOpt('pool_recycle', default=None,
+    cfg.IntOpt('pool_recycle', default=600,
                help="Lifetime of a connection (since creation) in seconds "
                     "or None for no recycling. Expired connections are "
                     "closed on acquire."),
-    cfg.IntOpt('pool_stale', default=None,
+    cfg.IntOpt('pool_stale', default=60,
                help="Threshold at which inactive (since release) connections "
                     "are considered stale in seconds or None for no "
                     "staleness. Stale connections are closed on acquire.")
@@ -87,7 +94,7 @@ notification_opts = [
              "sending notification, -1 means infinite retry."
     ),
     cfg.FloatOpt(
-        'notification_retry_delay', default=0.1,
+        'notification_retry_delay', default=0.25,
         help="Reconnecting retry delay in case of connectivity problem during "
              "sending notification message"
     )
@@ -101,23 +108,45 @@ rpc_opts = [
                help="Exchange name for for sending RPC messages"),
     cfg.StrOpt('rpc_reply_exchange', default="${control_exchange}_rpc_reply",
                help="Exchange name for for receiving RPC replies"),
+
+    cfg.BoolOpt('rpc_listener_ack', default=True,
+                help="Disable to increase performance. If disabled - some "
+                     "messages may be lost in case of connectivity problem. "
+                     "If enabled - may cause not needed message redelivery "
+                     "and rpc request could be processed more then one time"),
+    cfg.BoolOpt('rpc_reply_listener_ack', default=True,
+                help="Disable to increase performance. If disabled - some "
+                     "replies may be lost in case of connectivity problem."),
     cfg.IntOpt(
-        'rpc_reply_retry_attempts', default=3,
+        'rpc_listener_prefetch_count', default=10,
+        help="Max number of not acknowledged message which RabbitMQ can send "
+             "to rpc listener. Works only if rpc_listener_ack == True"
+    ),
+    cfg.IntOpt(
+        'rpc_reply_listener_prefetch_count', default=10,
+        help="Max number of not acknowledged message which RabbitMQ can send "
+             "to rpc reply listener. Works only if rpc_reply_listener_ack == "
+             "True"
+    ),
+    cfg.IntOpt(
+        'rpc_reply_retry_attempts', default=-1,
         help="Reconnecting retry count in case of connectivity problem during "
-             "sending reply. -1 means infinite retry."
+             "sending reply. -1 means infinite retry during rpc_timeout"
     ),
     cfg.FloatOpt(
-        'rpc_reply_retry_delay', default=0.1,
+        'rpc_reply_retry_delay', default=0.25,
         help="Reconnecting retry delay in case of connectivity problem during "
              "sending reply."
     ),
     cfg.IntOpt(
-        'default_rpc_retry_attempts', default=0,
+        'default_rpc_retry_attempts', default=-1,
         help="Reconnecting retry count in case of connectivity problem during "
-             "sending RPC message, -1 means infinite retry."
+             "sending RPC message, -1 means infinite retry. If actual "
+             "retry attempts in not 0 the rpc request could be processed more "
+             "then one time"
     ),
     cfg.FloatOpt(
-        'rpc_retry_delay', default=0.1,
+        'rpc_retry_delay', default=0.25,
         help="Reconnecting retry delay in case of connectivity problem during "
              "sending RPC message"
     )
@@ -147,10 +176,18 @@ class ConnectionException(exceptions.MessagingException):
     pass
 
 
+class HostConnectionNotAllowedException(ConnectionException):
+    pass
+
+
 class EstablishConnectionException(ConnectionException):
     pass
 
 
+class TimeoutConnectionException(ConnectionException):
+    pass
+
+
 class PooledConnectionWithConfirmations(pika_pool.Connection):
     @property
     def channel(self):
@@ -161,9 +198,16 @@ class PooledConnectionWithConfirmations(pika_pool.Connection):
 
 
 class PikaEngine(object):
+    HOST_CONNECTION_LAST_TRY_TIME = "last_try_time"
+    HOST_CONNECTION_LAST_SUCCESS_TRY_TIME = "last_success_try_time"
+
+    TCP_USER_TIMEOUT = 18
+
     def __init__(self, conf, url, default_exchange=None):
         self.conf = conf
 
+        # processing rpc options
+
         self.default_rpc_exchange = (
             conf.oslo_messaging_pika.default_rpc_exchange if
             conf.oslo_messaging_pika.default_rpc_exchange else
@@ -175,14 +219,18 @@ class PikaEngine(object):
             default_exchange
         )
 
-        self.default_notification_exchange = (
-            conf.oslo_messaging_pika.default_notification_exchange if
-            conf.oslo_messaging_pika.default_notification_exchange else
-            default_exchange
+        self.rpc_listener_ack = conf.oslo_messaging_pika.rpc_listener_ack
+
+        self.rpc_reply_listener_ack = (
+            conf.oslo_messaging_pika.rpc_reply_listener_ack
         )
 
-        self.notification_persistence = (
-            conf.oslo_messaging_pika.notification_persistence
+        self.rpc_listener_prefetch_count = (
+            conf.oslo_messaging_pika.rpc_listener_prefetch_count
+        )
+
+        self.rpc_reply_listener_prefetch_count = (
+            conf.oslo_messaging_pika.rpc_listener_prefetch_count
         )
 
         self.rpc_reply_retry_attempts = (
@@ -198,6 +246,17 @@ class PikaEngine(object):
             raise ValueError("rpc_reply_retry_delay should be non-negative "
                              "integer")
 
+        # processing notification options
+        self.default_notification_exchange = (
+            conf.oslo_messaging_pika.default_notification_exchange if
+            conf.oslo_messaging_pika.default_notification_exchange else
+            default_exchange
+        )
+
+        self.notification_persistence = (
+            conf.oslo_messaging_pika.notification_persistence
+        )
+
         self.default_rpc_retry_attempts = (
             conf.oslo_messaging_pika.default_rpc_retry_attempts
         )
@@ -235,32 +294,41 @@ class PikaEngine(object):
         self._reply_consumer_lock = threading.Lock()
         self._puller_thread = None
 
+        self._tcp_user_timeout = self.conf.oslo_messaging_pika.tcp_user_timeout
+        self._host_connection_reconnect_delay = (
+            self.conf.oslo_messaging_pika.host_connection_reconnect_delay
+        )
+
         # initializing connection parameters for configured RabbitMQ hosts
-        self._pika_next_connection_num = 0
         common_pika_params = {
             'virtual_host': url.virtual_host,
             'channel_max': self.conf.oslo_messaging_pika.channel_max,
             'frame_max': self.conf.oslo_messaging_pika.frame_max,
-            'heartbeat_interval':
-                self.conf.oslo_messaging_pika.heartbeat_interval,
             'ssl': self.conf.oslo_messaging_pika.ssl,
             'ssl_options': self.conf.oslo_messaging_pika.ssl_options,
             'socket_timeout': self.conf.oslo_messaging_pika.socket_timeout,
         }
 
-        self._pika_params_list = []
-        self._create_connection_lock = threading.Lock()
+        self._connection_lock = threading.Lock()
+
+        self._connection_host_param_list = []
+        self._connection_host_status_list = []
+        self._next_connection_host_num = 0
 
         for transport_host in url.hosts:
-            pika_params = pika.ConnectionParameters(
+            pika_params = common_pika_params.copy()
+            pika_params.update(
                 host=transport_host.hostname,
                 port=transport_host.port,
                 credentials=pika_credentials.PlainCredentials(
                     transport_host.username, transport_host.password
                 ),
-                **common_pika_params
             )
-            self._pika_params_list.append(pika_params)
+            self._connection_host_param_list.append(pika_params)
+            self._connection_host_status_list.append({
+                self.HOST_CONNECTION_LAST_TRY_TIME: 0,
+                self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME: 0
+            })
 
         # initializing 2 connection pools: 1st for connections without
         # confirmations, 2nd - with confirmations
@@ -286,40 +354,124 @@ class PikaEngine(object):
             PooledConnectionWithConfirmations
         )
 
-    def create_connection(self):
+    def _next_connection_num(self):
+        with self._connection_lock:
+            cur_num = self._next_connection_host_num
+            self._next_connection_host_num += 1
+            self._next_connection_host_num %= len(
+                self._connection_host_param_list
+            )
+        return cur_num
+
+    def create_connection(self, for_listening=False):
         """Create and return connection to any available host.
 
         :return: cerated connection
         :raise: ConnectionException if all hosts are not reachable
         """
-        host_num = len(self._pika_params_list)
-        connection_attempts = host_num
+        host_count = len(self._connection_host_param_list)
+        connection_attempts = host_count
+
+        pika_next_connection_num = self._next_connection_num()
+
         while connection_attempts > 0:
-            with self._create_connection_lock:
-                try:
-                    return self.create_host_connection(
-                        self._pika_next_connection_num
-                    )
-                except pika_pool.Connection.connectivity_errors as e:
-                    LOG.warn(str(e))
-                    connection_attempts -= 1
-                    continue
-                finally:
-                    self._pika_next_connection_num += 1
-                    self._pika_next_connection_num %= host_num
+            try:
+                return self.create_host_connection(
+                    pika_next_connection_num, for_listening
+                )
+            except pika_pool.Connection.connectivity_errors as e:
+                LOG.warn(str(e))
+            except HostConnectionNotAllowedException as e:
+                LOG.warn(str(e))
+
+            connection_attempts -= 1
+            pika_next_connection_num += 1
+            pika_next_connection_num %= host_count
+
         raise EstablishConnectionException(
             "Can not establish connection to any configured RabbitMQ host: " +
-            str(self._pika_params_list)
+            str(self._connection_host_param_list)
         )
 
-    def create_host_connection(self, host_index):
+    def _set_tcp_user_timeout(self, s):
+        if not self._tcp_user_timeout:
+            return
+        try:
+            s.setsockopt(
+                socket.IPPROTO_TCP, self.TCP_USER_TIMEOUT,
+                int(self._tcp_user_timeout * 1000)
+            )
+        except socket.error:
+            LOG.warn(
+                "Whoops, this kernel doesn't seem to support TCP_USER_TIMEOUT."
+            )
+
+    def create_host_connection(self, host_index, for_listening=False):
         """Create new connection to host #host_index
 
         :return: New connection
         """
-        return pika_adapters.BlockingConnection(
-            self._pika_params_list[host_index]
+
+        with self._connection_lock:
+            cur_time = time.time()
+
+            last_success_time = self._connection_host_status_list[host_index][
+                self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME
+            ]
+            last_time = self._connection_host_status_list[host_index][
+                self.HOST_CONNECTION_LAST_TRY_TIME
+            ]
+            if (last_time != last_success_time and
+                    cur_time - last_time <
+                    self._host_connection_reconnect_delay):
+                raise HostConnectionNotAllowedException(
+                    "Connection to host #{} is not allowed now because of "
+                    "previous failure".format(host_index)
+                )
+
+            try:
+                base_host_params = self._connection_host_param_list[host_index]
+
+                connection = pika_adapters.BlockingConnection(
+                    pika.ConnectionParameters(
+                        heartbeat_interval=(
+                            self.conf.oslo_messaging_pika.heartbeat_interval
+                            if for_listening else None
+                        ),
+                        **base_host_params
+                    )
+                )
+
+                self._set_tcp_user_timeout(connection._impl.socket)
+
+                self._connection_host_status_list[host_index][
+                    self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME
+                ] = cur_time
+
+                return connection
+            finally:
+                self._connection_host_status_list[host_index][
+                    self.HOST_CONNECTION_LAST_TRY_TIME
+                ] = cur_time
+
+    @staticmethod
+    def declare_queue_binding_by_channel(channel, exchange, queue, routing_key,
+                                         exchange_type, queue_expiration,
+                                         queue_auto_delete, durable):
+        channel.exchange_declare(
+            exchange, exchange_type, auto_delete=True, durable=durable
         )
+        arguments = {}
+
+        if queue_expiration > 0:
+            arguments['x-expires'] = queue_expiration * 1000
+
+        channel.queue_declare(
+            queue, auto_delete=queue_auto_delete, durable=durable,
+            arguments=arguments
+        )
+
+        channel.queue_bind(queue, exchange, routing_key)
 
     def declare_queue_binding(self, exchange, queue, routing_key,
                               exchange_type, queue_expiration,
@@ -331,20 +483,10 @@ class PikaEngine(object):
             )
         try:
             with self.connection_pool.acquire(timeout=timeout) as conn:
-                conn.channel.exchange_declare(
-                    exchange, exchange_type, auto_delete=True, durable=durable
+                self.declare_queue_binding_by_channel(
+                    conn.channel, exchange, queue, routing_key, exchange_type,
+                    queue_expiration, queue_auto_delete, durable
                 )
-                arguments = {}
-
-                if queue_expiration > 0:
-                    arguments['x-expires'] = queue_expiration * 1000
-
-                conn.channel.queue_declare(
-                    queue, auto_delete=queue_auto_delete, durable=durable,
-                    arguments=arguments
-                )
-
-                conn.channel.queue_bind(queue, exchange, routing_key)
         except pika_pool.Timeout as e:
             raise exceptions.MessagingTimeout(
                 "Timeout for current operation was expired. {}.".format(str(e))
@@ -369,11 +511,10 @@ class PikaEngine(object):
             raise exceptions.MessagingTimeout(
                 "Timeout for current operation was expired."
             )
-
         try:
             with pool.acquire(timeout=timeout) as conn:
                 if timeout is not None:
-                    properties.expiration = str(int(timeout))
+                    properties.expiration = str(int(timeout * 1000))
                 conn.channel.publish(
                     exchange=exchange,
                     routing_key=routing_key,
@@ -410,6 +551,7 @@ class PikaEngine(object):
                         body, properties, exchange, routing_key, str(e)
                     )
                 )
+
             raise ConnectionException(
                 "Connectivity problem detected during sending the message: "
                 "[body:{}, properties: {}] to target: [exchange:{}, "
@@ -417,6 +559,10 @@ class PikaEngine(object):
                     body, properties, exchange, routing_key, str(e)
                 )
             )
+        except socket.timeout:
+            raise TimeoutConnectionException(
+                "Socket timeout exceeded."
+            )
 
     def publish(self, exchange, routing_key, body, properties, confirm,
                 mandatory, expiration_time, retrier):
@@ -454,6 +600,8 @@ class PikaEngine(object):
                     pika_engine=self,
                     exchange=self.rpc_reply_exchange,
                     queue=self._reply_queue,
+                    no_ack=not self.rpc_reply_listener_ack,
+                    prefetch_count=self.rpc_reply_listener_prefetch_count
                 )
 
                 self._reply_listener.start(timeout=timeout)
@@ -473,6 +621,7 @@ class PikaEngine(object):
         while self._reply_consumer_thread_run_flag:
             try:
                 message = self._reply_listener.poll(timeout=1)
+                message.acknowledge()
                 if message is None:
                     continue
                 i = 0
@@ -528,9 +677,9 @@ class PikaIncomingMessage(object):
         self.content_encoding = getattr(properties, "content_encoding",
                                         "utf-8")
 
-        self.expiration = (
+        self.expiration_time = (
             None if properties.expiration is None else
-            int(properties.expiration)
+            time.time() + int(properties.expiration) / 1000
         )
 
         if self.content_type != "application/json":
@@ -584,7 +733,7 @@ class PikaIncomingMessage(object):
                 else self._pika_engine.rpc_reply_retry_attempts
             ),
             retry_on_exception=on_exception,
-            wait_fixed=self._pika_engine.rpc_reply_retry_delay,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
         )
 
         try:
@@ -601,7 +750,7 @@ class PikaIncomingMessage(object):
                 ),
                 confirm=True,
                 mandatory=False,
-                expiration_time=time.time() + self.expiration,
+                expiration_time=self.expiration_time,
                 retrier=retrier
             )
             LOG.debug(
@@ -618,18 +767,12 @@ class PikaIncomingMessage(object):
 
     def acknowledge(self):
         if not self._no_ack:
-            try:
-                self._channel.basic_ack(delivery_tag=self.delivery_tag)
-            except Exception:
-                LOG.exception("Unable to acknowledge the message")
+            self._channel.basic_ack(delivery_tag=self.delivery_tag)
 
     def requeue(self):
         if not self._no_ack:
-            try:
-                return self._channel.basic_nack(delivery_tag=self.delivery_tag,
-                                                requeue=True)
-            except Exception:
-                LOG.exception("Unable to requeue the message")
+            return self._channel.basic_nack(delivery_tag=self.delivery_tag,
+                                            requeue=True)
 
 
 class PikaOutgoingMessage(object):
@@ -669,7 +812,7 @@ class PikaOutgoingMessage(object):
         )
 
         expiration_time = (
-            None if timeout is None else timeout + time.time()
+            None if timeout is None else (timeout + time.time())
         )
 
         if wait_for_reply:
@@ -722,7 +865,9 @@ class PikaListener(object):
         self._message_queue = collections.deque()
 
     def _reconnect(self):
-        self._connection = self._pika_engine.create_connection()
+        self._connection = self._pika_engine.create_connection(
+            for_listening=True
+        )
         self._channel = self._connection.channel()
         self._channel.basic_qos(prefetch_count=self._prefetch_count)
 
@@ -763,12 +908,14 @@ class PikaListener(object):
             with self._lock:
                 if not self._started:
                     return None
-                if self._channel is None:
-                    self._reconnect()
+
                 try:
+                    if self._channel is None:
+                        self._reconnect()
                     self._connection.process_data_events()
-                except pika_pool.Connection.connectivity_errors:
+                except Exception:
                     self._cleanup()
+                    raise
             if timeout and time.time() - start > timeout:
                 return None
 
@@ -800,7 +947,7 @@ class PikaListener(object):
 
 
 class RpcServicePikaListener(PikaListener):
-    def __init__(self, pika_engine, target, no_ack=True, prefetch_count=1):
+    def __init__(self, pika_engine, target, no_ack, prefetch_count):
         self._target = target
 
         super(RpcServicePikaListener, self).__init__(
@@ -820,17 +967,20 @@ class RpcServicePikaListener(PikaListener):
             self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
         )
 
-        self._pika_engine.declare_queue_binding(
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
             exchange=exchange, queue=queue, routing_key=queue,
             exchange_type='direct', queue_expiration=queue_expiration,
             queue_auto_delete=False, durable=False
         )
-        self._pika_engine.declare_queue_binding(
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
             exchange=exchange, queue=server_queue, routing_key=server_queue,
             exchange_type='direct', queue_expiration=queue_expiration,
             queue_auto_delete=False, durable=False
         )
-        self._pika_engine.declare_queue_binding(
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
             exchange=fanout_exchange, queue=server_queue, routing_key="",
             exchange_type='fanout', queue_expiration=queue_expiration,
             queue_auto_delete=False, durable=False
@@ -849,8 +999,7 @@ class RpcServicePikaListener(PikaListener):
 
 
 class RpcReplyPikaListener(PikaListener):
-    def __init__(self, pika_engine, exchange, queue, no_ack=True,
-                 prefetch_count=1):
+    def __init__(self, pika_engine, exchange, queue, no_ack, prefetch_count):
         self._exchange = exchange
         self._queue = queue
 
@@ -863,7 +1012,8 @@ class RpcReplyPikaListener(PikaListener):
             self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
         )
 
-        self._pika_engine.declare_queue_binding(
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
             exchange=self._exchange, queue=self._queue,
             routing_key=self._queue, exchange_type='direct',
             queue_expiration=queue_expiration, queue_auto_delete=False,
@@ -881,8 +1031,8 @@ class RpcReplyPikaListener(PikaListener):
 
         retrier = retrying.retry(
             stop_max_attempt_number=self._pika_engine.rpc_reply_retry_attempts,
-            stop_max_delay=timeout,
-            wait_fixed=self._pika_engine.rpc_reply_retry_delay,
+            stop_max_delay=timeout * 1000,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
             retry_on_exception=on_exception,
         )
 
@@ -912,7 +1062,8 @@ class NotificationPikaListener(PikaListener):
         for target, priority in self._targets_and_priorities:
             routing_key = '%s.%s' % (target.topic, priority)
             queue = self._queue_name or routing_key
-            self._pika_engine.declare_queue_binding(
+            self._pika_engine.declare_queue_binding_by_channel(
+                channel=self._channel,
                 exchange=(
                     target.exchange or
                     self._pika_engine.default_notification_exchange
@@ -991,7 +1142,7 @@ class PikaDriver(object):
             retrying.retry(
                 stop_max_attempt_number=(None if retry == -1 else retry),
                 retry_on_exception=on_exception,
-                wait_fixed=self._pika_engine.rpc_retry_delay,
+                wait_fixed=self._pika_engine.rpc_retry_delay * 1000,
             )
         )
 
@@ -1045,7 +1196,7 @@ class PikaDriver(object):
                         queue=target.topic,
                         routing_key=target.topic,
                         exchange_type='direct',
-                        queue_expiration=False,
+                        queue_expiration=None,
                         queue_auto_delete=False,
                         durable=self._pika_engine.notification_persistence,
                     )
@@ -1054,6 +1205,7 @@ class PikaDriver(object):
                 return True
             elif isinstance(ex,
                             (ConnectionException, MessageRejectedException)):
+                LOG.warn(str(ex))
                 return True
             else:
                 return False
@@ -1061,7 +1213,7 @@ class PikaDriver(object):
         retrier = retrying.retry(
             stop_max_attempt_number=(None if retry == -1 else retry),
             retry_on_exception=on_exception,
-            wait_fixed=self._pika_engine.notification_retry_delay,
+            wait_fixed=self._pika_engine.notification_retry_delay * 1000,
         )
 
         msg = PikaOutgoingMessage(self._pika_engine, message, ctxt)
@@ -1080,7 +1232,11 @@ class PikaDriver(object):
         )
 
     def listen(self, target):
-        listener = RpcServicePikaListener(self._pika_engine, target)
+        listener = RpcServicePikaListener(
+            self._pika_engine, target,
+            no_ack=not self._pika_engine.rpc_listener_ack,
+            prefetch_count=self._pika_engine.rpc_listener_prefetch_count
+        )
         listener.start()
         return listener
 

From 8737dea0bee731ad74f4a00261932f98e00364bb Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Tue, 3 Nov 2015 17:26:56 +0200
Subject: [PATCH 05/16] Splits pika driver into several files

Change-Id: Ieff35e94b2a62137bc72820a8b1c83561dea5765
---
 oslo_messaging/_drivers/impl_pika.py          | 978 +-----------------
 .../_drivers/pika_driver/__init__.py          |   0
 .../_drivers/pika_driver/pika_engine.py       | 510 +++++++++
 .../_drivers/pika_driver/pika_exceptions.py   |  43 +
 .../_drivers/pika_driver/pika_listener.py     | 266 +++++
 .../_drivers/pika_driver/pika_message.py      | 217 ++++
 6 files changed, 1056 insertions(+), 958 deletions(-)
 create mode 100644 oslo_messaging/_drivers/pika_driver/__init__.py
 create mode 100644 oslo_messaging/_drivers/pika_driver/pika_engine.py
 create mode 100644 oslo_messaging/_drivers/pika_driver/pika_exceptions.py
 create mode 100644 oslo_messaging/_drivers/pika_driver/pika_listener.py
 create mode 100644 oslo_messaging/_drivers/pika_driver/pika_message.py

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index fc6de2c82..085901a16 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -11,35 +11,22 @@
 #    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 collections
-from concurrent import futures
 
-import pika
-from pika import adapters as pika_adapters
-from pika import credentials as pika_credentials
-from pika import exceptions as pika_exceptions
-from pika import spec as pika_spec
-
-import pika_pool
 import retrying
 
-import six
-import socket
 import sys
-import threading
-import time
-import uuid
-
 
 from oslo_config import cfg
 from oslo_log import log as logging
 
 from oslo_messaging._drivers import common
+from oslo_messaging._drivers.pika_driver import pika_engine
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
+from oslo_messaging._drivers.pika_driver import pika_listener
+from oslo_messaging._drivers.pika_driver import pika_message
+
 from oslo_messaging import exceptions
 
-from oslo_serialization import jsonutils
-
-
 LOG = logging.getLogger(__name__)
 
 pika_opts = [
@@ -160,935 +147,6 @@ def _is_eventlet_monkey_patched():
     return eventlet.patcher.is_monkey_patched('thread')
 
 
-class ExchangeNotFoundException(exceptions.MessageDeliveryFailure):
-    pass
-
-
-class MessageRejectedException(exceptions.MessageDeliveryFailure):
-    pass
-
-
-class RoutingException(exceptions.MessageDeliveryFailure):
-    pass
-
-
-class ConnectionException(exceptions.MessagingException):
-    pass
-
-
-class HostConnectionNotAllowedException(ConnectionException):
-    pass
-
-
-class EstablishConnectionException(ConnectionException):
-    pass
-
-
-class TimeoutConnectionException(ConnectionException):
-    pass
-
-
-class PooledConnectionWithConfirmations(pika_pool.Connection):
-    @property
-    def channel(self):
-        if self.fairy.channel is None:
-            self.fairy.channel = self.fairy.cxn.channel()
-            self.fairy.channel.confirm_delivery()
-        return self.fairy.channel
-
-
-class PikaEngine(object):
-    HOST_CONNECTION_LAST_TRY_TIME = "last_try_time"
-    HOST_CONNECTION_LAST_SUCCESS_TRY_TIME = "last_success_try_time"
-
-    TCP_USER_TIMEOUT = 18
-
-    def __init__(self, conf, url, default_exchange=None):
-        self.conf = conf
-
-        # processing rpc options
-
-        self.default_rpc_exchange = (
-            conf.oslo_messaging_pika.default_rpc_exchange if
-            conf.oslo_messaging_pika.default_rpc_exchange else
-            default_exchange
-        )
-        self.rpc_reply_exchange = (
-            conf.oslo_messaging_pika.rpc_reply_exchange if
-            conf.oslo_messaging_pika.rpc_reply_exchange else
-            default_exchange
-        )
-
-        self.rpc_listener_ack = conf.oslo_messaging_pika.rpc_listener_ack
-
-        self.rpc_reply_listener_ack = (
-            conf.oslo_messaging_pika.rpc_reply_listener_ack
-        )
-
-        self.rpc_listener_prefetch_count = (
-            conf.oslo_messaging_pika.rpc_listener_prefetch_count
-        )
-
-        self.rpc_reply_listener_prefetch_count = (
-            conf.oslo_messaging_pika.rpc_listener_prefetch_count
-        )
-
-        self.rpc_reply_retry_attempts = (
-            conf.oslo_messaging_pika.rpc_reply_retry_attempts
-        )
-        if self.rpc_reply_retry_attempts is None:
-            raise ValueError("rpc_reply_retry_attempts should be integer")
-        self.rpc_reply_retry_delay = (
-            conf.oslo_messaging_pika.rpc_reply_retry_delay
-        )
-        if (self.rpc_reply_retry_delay is None or
-                self.rpc_reply_retry_delay < 0):
-            raise ValueError("rpc_reply_retry_delay should be non-negative "
-                             "integer")
-
-        # processing notification options
-        self.default_notification_exchange = (
-            conf.oslo_messaging_pika.default_notification_exchange if
-            conf.oslo_messaging_pika.default_notification_exchange else
-            default_exchange
-        )
-
-        self.notification_persistence = (
-            conf.oslo_messaging_pika.notification_persistence
-        )
-
-        self.default_rpc_retry_attempts = (
-            conf.oslo_messaging_pika.default_rpc_retry_attempts
-        )
-        if self.default_rpc_retry_attempts is None:
-            raise ValueError("default_rpc_retry_attempts should be an integer")
-        self.rpc_retry_delay = (
-            conf.oslo_messaging_pika.rpc_retry_delay
-        )
-        if (self.rpc_retry_delay is None or
-                self.rpc_retry_delay < 0):
-            raise ValueError("rpc_retry_delay should be non-negative integer")
-
-        self.default_notification_retry_attempts = (
-            conf.oslo_messaging_pika.default_notification_retry_attempts
-        )
-        if self.default_notification_retry_attempts is None:
-            raise ValueError("default_notification_retry_attempts should be "
-                             "an integer")
-        self.notification_retry_delay = (
-            conf.oslo_messaging_pika.notification_retry_delay
-        )
-        if (self.notification_retry_delay is None or
-                self.notification_retry_delay < 0):
-            raise ValueError("notification_retry_delay should be non-negative "
-                             "integer")
-
-        # preparing poller for listening replies
-        self._reply_queue = None
-
-        self._reply_listener = None
-        self._reply_waiting_future_list = []
-
-        self._reply_consumer_enabled = False
-        self._reply_consumer_thread_run_flag = True
-        self._reply_consumer_lock = threading.Lock()
-        self._puller_thread = None
-
-        self._tcp_user_timeout = self.conf.oslo_messaging_pika.tcp_user_timeout
-        self._host_connection_reconnect_delay = (
-            self.conf.oslo_messaging_pika.host_connection_reconnect_delay
-        )
-
-        # initializing connection parameters for configured RabbitMQ hosts
-        common_pika_params = {
-            'virtual_host': url.virtual_host,
-            'channel_max': self.conf.oslo_messaging_pika.channel_max,
-            'frame_max': self.conf.oslo_messaging_pika.frame_max,
-            'ssl': self.conf.oslo_messaging_pika.ssl,
-            'ssl_options': self.conf.oslo_messaging_pika.ssl_options,
-            'socket_timeout': self.conf.oslo_messaging_pika.socket_timeout,
-        }
-
-        self._connection_lock = threading.Lock()
-
-        self._connection_host_param_list = []
-        self._connection_host_status_list = []
-        self._next_connection_host_num = 0
-
-        for transport_host in url.hosts:
-            pika_params = common_pika_params.copy()
-            pika_params.update(
-                host=transport_host.hostname,
-                port=transport_host.port,
-                credentials=pika_credentials.PlainCredentials(
-                    transport_host.username, transport_host.password
-                ),
-            )
-            self._connection_host_param_list.append(pika_params)
-            self._connection_host_status_list.append({
-                self.HOST_CONNECTION_LAST_TRY_TIME: 0,
-                self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME: 0
-            })
-
-        # initializing 2 connection pools: 1st for connections without
-        # confirmations, 2nd - with confirmations
-        self.connection_pool = pika_pool.QueuedPool(
-            create=self.create_connection,
-            max_size=self.conf.oslo_messaging_pika.pool_max_size,
-            max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
-            timeout=self.conf.oslo_messaging_pika.pool_timeout,
-            recycle=self.conf.oslo_messaging_pika.pool_recycle,
-            stale=self.conf.oslo_messaging_pika.pool_stale,
-        )
-
-        self.connection_with_confirmation_pool = pika_pool.QueuedPool(
-            create=self.create_connection,
-            max_size=self.conf.oslo_messaging_pika.pool_max_size,
-            max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
-            timeout=self.conf.oslo_messaging_pika.pool_timeout,
-            recycle=self.conf.oslo_messaging_pika.pool_recycle,
-            stale=self.conf.oslo_messaging_pika.pool_stale,
-        )
-
-        self.connection_with_confirmation_pool.Connection = (
-            PooledConnectionWithConfirmations
-        )
-
-    def _next_connection_num(self):
-        with self._connection_lock:
-            cur_num = self._next_connection_host_num
-            self._next_connection_host_num += 1
-            self._next_connection_host_num %= len(
-                self._connection_host_param_list
-            )
-        return cur_num
-
-    def create_connection(self, for_listening=False):
-        """Create and return connection to any available host.
-
-        :return: cerated connection
-        :raise: ConnectionException if all hosts are not reachable
-        """
-        host_count = len(self._connection_host_param_list)
-        connection_attempts = host_count
-
-        pika_next_connection_num = self._next_connection_num()
-
-        while connection_attempts > 0:
-            try:
-                return self.create_host_connection(
-                    pika_next_connection_num, for_listening
-                )
-            except pika_pool.Connection.connectivity_errors as e:
-                LOG.warn(str(e))
-            except HostConnectionNotAllowedException as e:
-                LOG.warn(str(e))
-
-            connection_attempts -= 1
-            pika_next_connection_num += 1
-            pika_next_connection_num %= host_count
-
-        raise EstablishConnectionException(
-            "Can not establish connection to any configured RabbitMQ host: " +
-            str(self._connection_host_param_list)
-        )
-
-    def _set_tcp_user_timeout(self, s):
-        if not self._tcp_user_timeout:
-            return
-        try:
-            s.setsockopt(
-                socket.IPPROTO_TCP, self.TCP_USER_TIMEOUT,
-                int(self._tcp_user_timeout * 1000)
-            )
-        except socket.error:
-            LOG.warn(
-                "Whoops, this kernel doesn't seem to support TCP_USER_TIMEOUT."
-            )
-
-    def create_host_connection(self, host_index, for_listening=False):
-        """Create new connection to host #host_index
-
-        :return: New connection
-        """
-
-        with self._connection_lock:
-            cur_time = time.time()
-
-            last_success_time = self._connection_host_status_list[host_index][
-                self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME
-            ]
-            last_time = self._connection_host_status_list[host_index][
-                self.HOST_CONNECTION_LAST_TRY_TIME
-            ]
-            if (last_time != last_success_time and
-                    cur_time - last_time <
-                    self._host_connection_reconnect_delay):
-                raise HostConnectionNotAllowedException(
-                    "Connection to host #{} is not allowed now because of "
-                    "previous failure".format(host_index)
-                )
-
-            try:
-                base_host_params = self._connection_host_param_list[host_index]
-
-                connection = pika_adapters.BlockingConnection(
-                    pika.ConnectionParameters(
-                        heartbeat_interval=(
-                            self.conf.oslo_messaging_pika.heartbeat_interval
-                            if for_listening else None
-                        ),
-                        **base_host_params
-                    )
-                )
-
-                self._set_tcp_user_timeout(connection._impl.socket)
-
-                self._connection_host_status_list[host_index][
-                    self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME
-                ] = cur_time
-
-                return connection
-            finally:
-                self._connection_host_status_list[host_index][
-                    self.HOST_CONNECTION_LAST_TRY_TIME
-                ] = cur_time
-
-    @staticmethod
-    def declare_queue_binding_by_channel(channel, exchange, queue, routing_key,
-                                         exchange_type, queue_expiration,
-                                         queue_auto_delete, durable):
-        channel.exchange_declare(
-            exchange, exchange_type, auto_delete=True, durable=durable
-        )
-        arguments = {}
-
-        if queue_expiration > 0:
-            arguments['x-expires'] = queue_expiration * 1000
-
-        channel.queue_declare(
-            queue, auto_delete=queue_auto_delete, durable=durable,
-            arguments=arguments
-        )
-
-        channel.queue_bind(queue, exchange, routing_key)
-
-    def declare_queue_binding(self, exchange, queue, routing_key,
-                              exchange_type, queue_expiration,
-                              queue_auto_delete, durable,
-                              timeout=None):
-        if timeout is not None and timeout < 0:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired."
-            )
-        try:
-            with self.connection_pool.acquire(timeout=timeout) as conn:
-                self.declare_queue_binding_by_channel(
-                    conn.channel, exchange, queue, routing_key, exchange_type,
-                    queue_expiration, queue_auto_delete, durable
-                )
-        except pika_pool.Timeout as e:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired. {}.".format(str(e))
-            )
-        except pika_pool.Connection.connectivity_errors as e:
-            raise ConnectionException(
-                "Connectivity problem detected during declaring queue "
-                "binding: exchange:{}, queue: {}, routing_key: {}, "
-                "exchange_type: {}, queue_expiration: {}, queue_auto_delete: "
-                "{}, durable: {}. {}".format(
-                    exchange, queue, routing_key, exchange_type,
-                    queue_expiration, queue_auto_delete, durable, str(e)
-                )
-            )
-
-    @staticmethod
-    def _do_publish(pool, exchange, routing_key, body, properties,
-                    mandatory, expiration_time):
-        timeout = (None if expiration_time is None else
-                   expiration_time - time.time())
-        if timeout is not None and timeout < 0:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired."
-            )
-        try:
-            with pool.acquire(timeout=timeout) as conn:
-                if timeout is not None:
-                    properties.expiration = str(int(timeout * 1000))
-                conn.channel.publish(
-                    exchange=exchange,
-                    routing_key=routing_key,
-                    body=body,
-                    properties=properties,
-                    mandatory=mandatory
-                )
-        except pika_exceptions.NackError as e:
-            raise MessageRejectedException(
-                "Can not send message: [body: {}], properties: {}] to "
-                "target [exchange: {}, routing_key: {}]. {}".format(
-                    body, properties, exchange, routing_key, str(e)
-                )
-            )
-        except pika_exceptions.UnroutableError as e:
-            raise RoutingException(
-                "Can not deliver message:[body:{}, properties: {}] to any"
-                "queue using target: [exchange:{}, "
-                "routing_key:{}]. {}".format(
-                    body, properties, exchange, routing_key, str(e)
-                )
-            )
-        except pika_pool.Timeout as e:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired. {}".format(str(e))
-            )
-        except pika_pool.Connection.connectivity_errors as e:
-            if (isinstance(e, pika_exceptions.ChannelClosed)
-                    and e.args and e.args[0] == 404):
-                raise ExchangeNotFoundException(
-                    "Attempt to send message to not existing exchange "
-                    "detected, message: [body:{}, properties: {}], target: "
-                    "[exchange:{}, routing_key:{}]. {}".format(
-                        body, properties, exchange, routing_key, str(e)
-                    )
-                )
-
-            raise ConnectionException(
-                "Connectivity problem detected during sending the message: "
-                "[body:{}, properties: {}] to target: [exchange:{}, "
-                "routing_key:{}]. {}".format(
-                    body, properties, exchange, routing_key, str(e)
-                )
-            )
-        except socket.timeout:
-            raise TimeoutConnectionException(
-                "Socket timeout exceeded."
-            )
-
-    def publish(self, exchange, routing_key, body, properties, confirm,
-                mandatory, expiration_time, retrier):
-        pool = (self.connection_with_confirmation_pool if confirm else
-                self.connection_pool)
-
-        LOG.debug(
-            "Sending message:[body:{}; properties: {}] to target: "
-            "[exchange:{}; routing_key:{}]".format(
-                body, properties, exchange, routing_key
-            )
-        )
-
-        do_publish = (self._do_publish if retrier is None else
-                      retrier(self._do_publish))
-
-        return do_publish(pool, exchange, routing_key, body, properties,
-                          mandatory, expiration_time)
-
-    def get_reply_q(self, timeout=None):
-        if self._reply_consumer_enabled:
-            return self._reply_queue
-
-        with self._reply_consumer_lock:
-            if self._reply_consumer_enabled:
-                return self._reply_queue
-
-            if self._reply_queue is None:
-                self._reply_queue = "reply.{}.{}.{}".format(
-                    self.conf.project, self.conf.prog, uuid.uuid4().hex
-                )
-
-            if self._reply_listener is None:
-                self._reply_listener = RpcReplyPikaListener(
-                    pika_engine=self,
-                    exchange=self.rpc_reply_exchange,
-                    queue=self._reply_queue,
-                    no_ack=not self.rpc_reply_listener_ack,
-                    prefetch_count=self.rpc_reply_listener_prefetch_count
-                )
-
-                self._reply_listener.start(timeout=timeout)
-
-            if self._puller_thread is None:
-                self._puller_thread = threading.Thread(target=self._poller)
-                self._puller_thread.daemon = True
-
-            if not self._puller_thread.is_alive():
-                self._puller_thread.start()
-
-            self._reply_consumer_enabled = True
-
-        return self._reply_queue
-
-    def _poller(self):
-        while self._reply_consumer_thread_run_flag:
-            try:
-                message = self._reply_listener.poll(timeout=1)
-                message.acknowledge()
-                if message is None:
-                    continue
-                i = 0
-                curtime = time.time()
-                while (i < len(self._reply_waiting_future_list) and
-                        self._reply_consumer_thread_run_flag):
-                    msg_id, future, expiration = (
-                        self._reply_waiting_future_list[i]
-                    )
-                    if expiration and expiration < curtime:
-                        del self._reply_waiting_future_list[i]
-                    elif msg_id == message.msg_id:
-                        del self._reply_waiting_future_list[i]
-                        future.set_result(message)
-                    else:
-                        i += 1
-            except BaseException:
-                LOG.exception("Exception during reply polling")
-
-    def register_reply_waiter(self, msg_id, future, expiration_time):
-        self._reply_waiting_future_list.append(
-            (msg_id, future, expiration_time)
-        )
-
-    def cleanup(self):
-        with self._reply_consumer_lock:
-            self._reply_consumer_enabled = False
-
-            if self._puller_thread:
-                if self._puller_thread.is_alive():
-                    self._reply_consumer_thread_run_flag = False
-                    self._puller_thread.join()
-                self._puller_thread = None
-
-            if self._reply_listener:
-                self._reply_listener.stop()
-                self._reply_listener.cleanup()
-                self._reply_listener = None
-
-                self._reply_queue = None
-
-
-class PikaIncomingMessage(object):
-
-    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
-        self._pika_engine = pika_engine
-        self._no_ack = no_ack
-        self._channel = channel
-        self.delivery_tag = method.delivery_tag
-
-        self.content_type = getattr(properties, "content_type",
-                                    "application/json")
-        self.content_encoding = getattr(properties, "content_encoding",
-                                        "utf-8")
-
-        self.expiration_time = (
-            None if properties.expiration is None else
-            time.time() + int(properties.expiration) / 1000
-        )
-
-        if self.content_type != "application/json":
-            raise NotImplementedError("Content-type['{}'] is not valid, "
-                                      "'application/json' only is supported.")
-
-        message_dict = common.deserialize_msg(
-            jsonutils.loads(body, encoding=self.content_encoding)
-        )
-
-        self.unique_id = message_dict.pop('_unique_id')
-        self.msg_id = message_dict.pop('_msg_id', None)
-        self.reply_q = message_dict.pop('_reply_q', None)
-
-        context_dict = {}
-
-        for key in list(message_dict.keys()):
-            key = six.text_type(key)
-            if key.startswith('_context_'):
-                value = message_dict.pop(key)
-                context_dict[key[9:]] = value
-
-        self.message = message_dict
-        self.ctxt = context_dict
-
-    def reply(self, reply=None, failure=None, log_failure=True):
-        if not (self.msg_id and self.reply_q):
-            return
-
-        if failure:
-            failure = common.serialize_remote_exception(failure, log_failure)
-
-        msg = {
-            'result': reply,
-            'failure': failure,
-            '_unique_id': uuid.uuid4().hex,
-            '_msg_id': self.msg_id,
-            'ending': True
-        }
-
-        def on_exception(ex):
-            if isinstance(ex, ConnectionException):
-                LOG.warn(str(ex))
-                return True
-            else:
-                return False
-
-        retrier = retrying.retry(
-            stop_max_attempt_number=(
-                None if self._pika_engine.rpc_reply_retry_attempts == -1
-                else self._pika_engine.rpc_reply_retry_attempts
-            ),
-            retry_on_exception=on_exception,
-            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
-        )
-
-        try:
-            self._pika_engine.publish(
-                exchange=self._pika_engine.rpc_reply_exchange,
-                routing_key=self.reply_q,
-                body=jsonutils.dumps(
-                    common.serialize_msg(msg),
-                    encoding=self.content_encoding
-                ),
-                properties=pika_spec.BasicProperties(
-                    content_encoding=self.content_encoding,
-                    content_type=self.content_type,
-                ),
-                confirm=True,
-                mandatory=False,
-                expiration_time=self.expiration_time,
-                retrier=retrier
-            )
-            LOG.debug(
-                "Message [id:'{}'] replied to '{}'.".format(
-                    self.msg_id, self.reply_q
-                )
-            )
-        except Exception:
-            LOG.exception(
-                "Message [id:'{}'] wasn't replied to : {}".format(
-                    self.msg_id, self.reply_q
-                )
-            )
-
-    def acknowledge(self):
-        if not self._no_ack:
-            self._channel.basic_ack(delivery_tag=self.delivery_tag)
-
-    def requeue(self):
-        if not self._no_ack:
-            return self._channel.basic_nack(delivery_tag=self.delivery_tag,
-                                            requeue=True)
-
-
-class PikaOutgoingMessage(object):
-
-    def __init__(self, pika_engine, message, context,
-                 content_type="application/json", content_encoding="utf-8"):
-        self._pika_engine = pika_engine
-
-        self.content_type = content_type
-        self.content_encoding = content_encoding
-
-        if self.content_type != "application/json":
-            raise NotImplementedError("Content-type['{}'] is not valid, "
-                                      "'application/json' only is supported.")
-
-        self.message = message
-        self.context = context
-
-        self.unique_id = uuid.uuid4().hex
-        self.msg_id = None
-
-    def send(self, exchange, routing_key='', confirm=True,
-             wait_for_reply=False, mandatory=True, persistent=False,
-             timeout=None, retrier=None):
-        msg = self.message.copy()
-
-        msg['_unique_id'] = self.unique_id
-
-        for key, value in self.context.iteritems():
-            key = six.text_type(key)
-            msg['_context_' + key] = value
-
-        properties = pika_spec.BasicProperties(
-            content_encoding=self.content_encoding,
-            content_type=self.content_type,
-            delivery_mode=2 if persistent else 1
-        )
-
-        expiration_time = (
-            None if timeout is None else (timeout + time.time())
-        )
-
-        if wait_for_reply:
-            self.msg_id = uuid.uuid4().hex
-            msg['_msg_id'] = self.msg_id
-            LOG.debug('MSG_ID is %s', self.msg_id)
-
-            msg['_reply_q'] = self._pika_engine.get_reply_q(timeout)
-
-            future = futures.Future()
-
-            self._pika_engine.register_reply_waiter(
-                msg_id=self.msg_id, future=future,
-                expiration_time=expiration_time
-            )
-
-        self._pika_engine.publish(
-            exchange=exchange, routing_key=routing_key,
-            body=jsonutils.dumps(
-                common.serialize_msg(msg),
-                encoding=self.content_encoding
-            ),
-            properties=properties,
-            confirm=confirm,
-            mandatory=mandatory,
-            expiration_time=expiration_time,
-            retrier=retrier
-        )
-
-        if wait_for_reply:
-            try:
-                return future.result(timeout)
-            except futures.TimeoutError:
-                raise exceptions.MessagingTimeout()
-
-
-class PikaListener(object):
-    def __init__(self, pika_engine, no_ack, prefetch_count):
-        self._pika_engine = pika_engine
-
-        self._connection = None
-        self._channel = None
-        self._lock = threading.Lock()
-
-        self._prefetch_count = prefetch_count
-        self._no_ack = no_ack
-
-        self._started = False
-
-        self._message_queue = collections.deque()
-
-    def _reconnect(self):
-        self._connection = self._pika_engine.create_connection(
-            for_listening=True
-        )
-        self._channel = self._connection.channel()
-        self._channel.basic_qos(prefetch_count=self._prefetch_count)
-
-        self._on_reconnected()
-
-    def _on_reconnected(self):
-        raise NotImplementedError(
-            "It is base class. Please declare consumers here"
-        )
-
-    def _start_consuming(self, queue):
-        self._channel.basic_consume(self._on_message_callback,
-                                    queue, no_ack=self._no_ack)
-
-    def _on_message_callback(self, unused, method, properties, body):
-        self._message_queue.append((self._channel, method, properties, body))
-
-    def _cleanup(self):
-        if self._channel:
-            try:
-                self._channel.close()
-            except Exception as ex:
-                if not pika_pool.Connection.is_connection_invalidated(ex):
-                    LOG.exception("Unexpected error during closing channel")
-            self._channel = None
-
-        if self._connection:
-            try:
-                self._connection.close()
-            except Exception as ex:
-                if not pika_pool.Connection.is_connection_invalidated(ex):
-                    LOG.exception("Unexpected error during closing connection")
-            self._connection = None
-
-    def poll(self, timeout=None):
-        start = time.time()
-        while not self._message_queue:
-            with self._lock:
-                if not self._started:
-                    return None
-
-                try:
-                    if self._channel is None:
-                        self._reconnect()
-                    self._connection.process_data_events()
-                except Exception:
-                    self._cleanup()
-                    raise
-            if timeout and time.time() - start > timeout:
-                return None
-
-        return self._message_queue.popleft()
-
-    def start(self):
-        self._started = True
-
-    def stop(self):
-        with self._lock:
-            if not self._started:
-                return
-
-            self._started = False
-            self._cleanup()
-
-    def reconnect(self):
-        with self._lock:
-            self._cleanup()
-            try:
-                self._reconnect()
-            except Exception:
-                self._cleanup()
-                raise
-
-    def cleanup(self):
-        with self._lock:
-            self._cleanup()
-
-
-class RpcServicePikaListener(PikaListener):
-    def __init__(self, pika_engine, target, no_ack, prefetch_count):
-        self._target = target
-
-        super(RpcServicePikaListener, self).__init__(
-            pika_engine, no_ack=no_ack, prefetch_count=prefetch_count)
-
-    def _on_reconnected(self):
-        exchange = (self._target.exchange or
-                    self._pika_engine.default_rpc_exchange)
-        queue = '{}'.format(self._target.topic)
-        server_queue = '{}.{}'.format(queue, self._target.server)
-
-        fanout_exchange = '{}_fanout_{}'.format(
-            self._pika_engine.default_rpc_exchange, self._target.topic
-        )
-
-        queue_expiration = (
-            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
-        )
-
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=exchange, queue=queue, routing_key=queue,
-            exchange_type='direct', queue_expiration=queue_expiration,
-            queue_auto_delete=False, durable=False
-        )
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=exchange, queue=server_queue, routing_key=server_queue,
-            exchange_type='direct', queue_expiration=queue_expiration,
-            queue_auto_delete=False, durable=False
-        )
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=fanout_exchange, queue=server_queue, routing_key="",
-            exchange_type='fanout', queue_expiration=queue_expiration,
-            queue_auto_delete=False, durable=False
-        )
-
-        self._start_consuming(queue)
-        self._start_consuming(server_queue)
-
-    def poll(self, timeout=None):
-        msg = super(RpcServicePikaListener, self).poll(timeout)
-        if msg is None:
-            return None
-        return PikaIncomingMessage(
-            self._pika_engine, *msg, no_ack=self._no_ack
-        )
-
-
-class RpcReplyPikaListener(PikaListener):
-    def __init__(self, pika_engine, exchange, queue, no_ack, prefetch_count):
-        self._exchange = exchange
-        self._queue = queue
-
-        super(RpcReplyPikaListener, self).__init__(
-            pika_engine, no_ack, prefetch_count
-        )
-
-    def _on_reconnected(self):
-        queue_expiration = (
-            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
-        )
-
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=self._exchange, queue=self._queue,
-            routing_key=self._queue, exchange_type='direct',
-            queue_expiration=queue_expiration, queue_auto_delete=False,
-            durable=False
-        )
-        self._start_consuming(self._queue)
-
-    def start(self, timeout=None):
-        super(RpcReplyPikaListener, self).start()
-
-        def on_exception(ex):
-            LOG.warn(str(ex))
-
-            return True
-
-        retrier = retrying.retry(
-            stop_max_attempt_number=self._pika_engine.rpc_reply_retry_attempts,
-            stop_max_delay=timeout * 1000,
-            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
-            retry_on_exception=on_exception,
-        )
-
-        retrier(self.reconnect)()
-
-    def poll(self, timeout=None):
-        msg = super(RpcReplyPikaListener, self).poll(timeout)
-        if msg is None:
-            return None
-        return PikaIncomingMessage(
-            self._pika_engine, *msg, no_ack=self._no_ack
-        )
-
-
-class NotificationPikaListener(PikaListener):
-    def __init__(self, pika_engine, targets_and_priorities,
-                 queue_name=None, prefetch_count=100):
-        self._targets_and_priorities = targets_and_priorities
-        self._queue_name = queue_name
-
-        super(NotificationPikaListener, self).__init__(
-            pika_engine, no_ack=False, prefetch_count=prefetch_count
-        )
-
-    def _on_reconnected(self):
-        queues_to_consume = set()
-        for target, priority in self._targets_and_priorities:
-            routing_key = '%s.%s' % (target.topic, priority)
-            queue = self._queue_name or routing_key
-            self._pika_engine.declare_queue_binding_by_channel(
-                channel=self._channel,
-                exchange=(
-                    target.exchange or
-                    self._pika_engine.default_notification_exchange
-                ),
-                queue = queue,
-                routing_key=routing_key,
-                exchange_type='direct',
-                queue_expiration=None,
-                queue_auto_delete=False,
-                durable=self._pika_engine.notification_persistence,
-            )
-            queues_to_consume.add(queue)
-
-        for queue_to_consume in queues_to_consume:
-            self._start_consuming(queue_to_consume)
-
-    def poll(self, timeout=None):
-        msg = super(NotificationPikaListener, self).poll(timeout)
-        if msg is None:
-            return None
-        return PikaIncomingMessage(
-            self._pika_engine, *msg, no_ack=self._no_ack
-        )
-
-
 class PikaDriver(object):
     def __init__(self, conf, url, default_exchange=None,
                  allowed_remote_exmods=None):
@@ -1118,7 +176,7 @@ class PikaDriver(object):
         self.conf = conf
         self._allowed_remote_exmods = allowed_remote_exmods
 
-        self._pika_engine = PikaEngine(conf, url, default_exchange)
+        self._pika_engine = pika_engine.PikaEngine(conf, url, default_exchange)
 
     def require_features(self, requeue=False):
         pass
@@ -1130,7 +188,7 @@ class PikaDriver(object):
             retry = self._pika_engine.default_rpc_retry_attempts
 
         def on_exception(ex):
-            if isinstance(ex, (ConnectionException,
+            if isinstance(ex, (pika_drv_exc.ConnectionException,
                                exceptions.MessageDeliveryFailure)):
                 LOG.warn(str(ex))
                 return True
@@ -1146,7 +204,8 @@ class PikaDriver(object):
             )
         )
 
-        msg = PikaOutgoingMessage(self._pika_engine, message, ctxt)
+        msg = pika_message.PikaOutgoingMessage(self._pika_engine, message,
+                                               ctxt)
 
         if target.fanout:
             return msg.send(
@@ -1185,7 +244,8 @@ class PikaDriver(object):
             retry = self._pika_engine.default_notification_retry_attempts
 
         def on_exception(ex):
-            if isinstance(ex, (ExchangeNotFoundException, RoutingException)):
+            if isinstance(ex, (pika_drv_exc.ExchangeNotFoundException,
+                               pika_drv_exc.RoutingException)):
                 LOG.warn(str(ex))
                 try:
                     self._pika_engine.declare_queue_binding(
@@ -1200,11 +260,11 @@ class PikaDriver(object):
                         queue_auto_delete=False,
                         durable=self._pika_engine.notification_persistence,
                     )
-                except ConnectionException as e:
+                except pika_drv_exc.ConnectionException as e:
                     LOG.warn(str(e))
                 return True
-            elif isinstance(ex,
-                            (ConnectionException, MessageRejectedException)):
+            elif isinstance(ex, (pika_drv_exc.ConnectionException,
+                                 pika_drv_exc.MessageRejectedException)):
                 LOG.warn(str(ex))
                 return True
             else:
@@ -1216,7 +276,8 @@ class PikaDriver(object):
             wait_fixed=self._pika_engine.notification_retry_delay * 1000,
         )
 
-        msg = PikaOutgoingMessage(self._pika_engine, message, ctxt)
+        msg = pika_message.PikaOutgoingMessage(self._pika_engine, message,
+                                               ctxt)
 
         return msg.send(
             exchange=(
@@ -1232,7 +293,7 @@ class PikaDriver(object):
         )
 
     def listen(self, target):
-        listener = RpcServicePikaListener(
+        listener = pika_listener.RpcServicePikaListener(
             self._pika_engine, target,
             no_ack=not self._pika_engine.rpc_listener_ack,
             prefetch_count=self._pika_engine.rpc_listener_prefetch_count
@@ -1241,8 +302,9 @@ class PikaDriver(object):
         return listener
 
     def listen_for_notifications(self, targets_and_priorities, pool):
-        listener = NotificationPikaListener(self._pika_engine,
-                                            targets_and_priorities, pool)
+        listener = pika_listener.NotificationPikaListener(
+            self._pika_engine, targets_and_priorities, pool
+        )
         listener.start()
         return listener
 
diff --git a/oslo_messaging/_drivers/pika_driver/__init__.py b/oslo_messaging/_drivers/pika_driver/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
new file mode 100644
index 000000000..991824d64
--- /dev/null
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -0,0 +1,510 @@
+#    Copyright 2015 Mirantis, 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 socket
+
+from oslo_log import log as logging
+
+from oslo_messaging import exceptions
+
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
+from oslo_messaging._drivers.pika_driver import pika_listener
+
+import pika
+from pika import adapters as pika_adapters
+from pika import credentials as pika_credentials
+from pika import exceptions as pika_exceptions
+
+import pika_pool
+
+import threading
+import time
+import uuid
+
+LOG = logging.getLogger(__name__)
+
+
+class PooledConnectionWithConfirmations(pika_pool.Connection):
+    @property
+    def channel(self):
+        if self.fairy.channel is None:
+            self.fairy.channel = self.fairy.cxn.channel()
+            self.fairy.channel.confirm_delivery()
+        return self.fairy.channel
+
+
+class PikaEngine(object):
+    HOST_CONNECTION_LAST_TRY_TIME = "last_try_time"
+    HOST_CONNECTION_LAST_SUCCESS_TRY_TIME = "last_success_try_time"
+
+    TCP_USER_TIMEOUT = 18
+
+    def __init__(self, conf, url, default_exchange=None):
+        self.conf = conf
+
+        # processing rpc options
+
+        self.default_rpc_exchange = (
+            conf.oslo_messaging_pika.default_rpc_exchange if
+            conf.oslo_messaging_pika.default_rpc_exchange else
+            default_exchange
+        )
+        self.rpc_reply_exchange = (
+            conf.oslo_messaging_pika.rpc_reply_exchange if
+            conf.oslo_messaging_pika.rpc_reply_exchange else
+            default_exchange
+        )
+
+        self.rpc_listener_ack = conf.oslo_messaging_pika.rpc_listener_ack
+
+        self.rpc_reply_listener_ack = (
+            conf.oslo_messaging_pika.rpc_reply_listener_ack
+        )
+
+        self.rpc_listener_prefetch_count = (
+            conf.oslo_messaging_pika.rpc_listener_prefetch_count
+        )
+
+        self.rpc_reply_listener_prefetch_count = (
+            conf.oslo_messaging_pika.rpc_listener_prefetch_count
+        )
+
+        self.rpc_reply_retry_attempts = (
+            conf.oslo_messaging_pika.rpc_reply_retry_attempts
+        )
+        if self.rpc_reply_retry_attempts is None:
+            raise ValueError("rpc_reply_retry_attempts should be integer")
+        self.rpc_reply_retry_delay = (
+            conf.oslo_messaging_pika.rpc_reply_retry_delay
+        )
+        if (self.rpc_reply_retry_delay is None or
+                self.rpc_reply_retry_delay < 0):
+            raise ValueError("rpc_reply_retry_delay should be non-negative "
+                             "integer")
+
+        # processing notification options
+        self.default_notification_exchange = (
+            conf.oslo_messaging_pika.default_notification_exchange if
+            conf.oslo_messaging_pika.default_notification_exchange else
+            default_exchange
+        )
+
+        self.notification_persistence = (
+            conf.oslo_messaging_pika.notification_persistence
+        )
+
+        self.default_rpc_retry_attempts = (
+            conf.oslo_messaging_pika.default_rpc_retry_attempts
+        )
+        if self.default_rpc_retry_attempts is None:
+            raise ValueError("default_rpc_retry_attempts should be an integer")
+        self.rpc_retry_delay = (
+            conf.oslo_messaging_pika.rpc_retry_delay
+        )
+        if (self.rpc_retry_delay is None or
+                self.rpc_retry_delay < 0):
+            raise ValueError("rpc_retry_delay should be non-negative integer")
+
+        self.default_notification_retry_attempts = (
+            conf.oslo_messaging_pika.default_notification_retry_attempts
+        )
+        if self.default_notification_retry_attempts is None:
+            raise ValueError("default_notification_retry_attempts should be "
+                             "an integer")
+        self.notification_retry_delay = (
+            conf.oslo_messaging_pika.notification_retry_delay
+        )
+        if (self.notification_retry_delay is None or
+                self.notification_retry_delay < 0):
+            raise ValueError("notification_retry_delay should be non-negative "
+                             "integer")
+
+        # preparing poller for listening replies
+        self._reply_queue = None
+
+        self._reply_listener = None
+        self._reply_waiting_future_list = []
+
+        self._reply_consumer_enabled = False
+        self._reply_consumer_thread_run_flag = True
+        self._reply_consumer_lock = threading.Lock()
+        self._puller_thread = None
+
+        self._tcp_user_timeout = self.conf.oslo_messaging_pika.tcp_user_timeout
+        self._host_connection_reconnect_delay = (
+            self.conf.oslo_messaging_pika.host_connection_reconnect_delay
+        )
+
+        # initializing connection parameters for configured RabbitMQ hosts
+        common_pika_params = {
+            'virtual_host': url.virtual_host,
+            'channel_max': self.conf.oslo_messaging_pika.channel_max,
+            'frame_max': self.conf.oslo_messaging_pika.frame_max,
+            'ssl': self.conf.oslo_messaging_pika.ssl,
+            'ssl_options': self.conf.oslo_messaging_pika.ssl_options,
+            'socket_timeout': self.conf.oslo_messaging_pika.socket_timeout,
+        }
+
+        self._connection_lock = threading.Lock()
+
+        self._connection_host_param_list = []
+        self._connection_host_status_list = []
+        self._next_connection_host_num = 0
+
+        for transport_host in url.hosts:
+            pika_params = common_pika_params.copy()
+            pika_params.update(
+                host=transport_host.hostname,
+                port=transport_host.port,
+                credentials=pika_credentials.PlainCredentials(
+                    transport_host.username, transport_host.password
+                ),
+            )
+            self._connection_host_param_list.append(pika_params)
+            self._connection_host_status_list.append({
+                self.HOST_CONNECTION_LAST_TRY_TIME: 0,
+                self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME: 0
+            })
+
+        # initializing 2 connection pools: 1st for connections without
+        # confirmations, 2nd - with confirmations
+        self.connection_pool = pika_pool.QueuedPool(
+            create=self.create_connection,
+            max_size=self.conf.oslo_messaging_pika.pool_max_size,
+            max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
+            timeout=self.conf.oslo_messaging_pika.pool_timeout,
+            recycle=self.conf.oslo_messaging_pika.pool_recycle,
+            stale=self.conf.oslo_messaging_pika.pool_stale,
+        )
+
+        self.connection_with_confirmation_pool = pika_pool.QueuedPool(
+            create=self.create_connection,
+            max_size=self.conf.oslo_messaging_pika.pool_max_size,
+            max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
+            timeout=self.conf.oslo_messaging_pika.pool_timeout,
+            recycle=self.conf.oslo_messaging_pika.pool_recycle,
+            stale=self.conf.oslo_messaging_pika.pool_stale,
+        )
+
+        self.connection_with_confirmation_pool.Connection = (
+            PooledConnectionWithConfirmations
+        )
+
+    def _next_connection_num(self):
+        with self._connection_lock:
+            cur_num = self._next_connection_host_num
+            self._next_connection_host_num += 1
+            self._next_connection_host_num %= len(
+                self._connection_host_param_list
+            )
+        return cur_num
+
+    def create_connection(self, for_listening=False):
+        """Create and return connection to any available host.
+
+        :return: cerated connection
+        :raise: ConnectionException if all hosts are not reachable
+        """
+        host_count = len(self._connection_host_param_list)
+        connection_attempts = host_count
+
+        pika_next_connection_num = self._next_connection_num()
+
+        while connection_attempts > 0:
+            try:
+                return self.create_host_connection(
+                    pika_next_connection_num, for_listening
+                )
+            except pika_pool.Connection.connectivity_errors as e:
+                LOG.warn(str(e))
+            except pika_drv_exc.HostConnectionNotAllowedException as e:
+                LOG.warn(str(e))
+
+            connection_attempts -= 1
+            pika_next_connection_num += 1
+            pika_next_connection_num %= host_count
+
+        raise pika_drv_exc.EstablishConnectionException(
+            "Can not establish connection to any configured RabbitMQ host: " +
+            str(self._connection_host_param_list)
+        )
+
+    def _set_tcp_user_timeout(self, s):
+        if not self._tcp_user_timeout:
+            return
+        try:
+            s.setsockopt(
+                socket.IPPROTO_TCP, self.TCP_USER_TIMEOUT,
+                int(self._tcp_user_timeout * 1000)
+            )
+        except socket.error:
+            LOG.warn(
+                "Whoops, this kernel doesn't seem to support TCP_USER_TIMEOUT."
+            )
+
+    def create_host_connection(self, host_index, for_listening=False):
+        """Create new connection to host #host_index
+
+        :return: New connection
+        """
+
+        with self._connection_lock:
+            cur_time = time.time()
+
+            last_success_time = self._connection_host_status_list[host_index][
+                self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME
+            ]
+            last_time = self._connection_host_status_list[host_index][
+                self.HOST_CONNECTION_LAST_TRY_TIME
+            ]
+            if (last_time != last_success_time and
+                    cur_time - last_time <
+                    self._host_connection_reconnect_delay):
+                raise pika_drv_exc.HostConnectionNotAllowedException(
+                    "Connection to host #{} is not allowed now because of "
+                    "previous failure".format(host_index)
+                )
+
+            try:
+                base_host_params = self._connection_host_param_list[host_index]
+
+                connection = pika_adapters.BlockingConnection(
+                    pika.ConnectionParameters(
+                        heartbeat_interval=(
+                            self.conf.oslo_messaging_pika.heartbeat_interval
+                            if for_listening else None
+                        ),
+                        **base_host_params
+                    )
+                )
+
+                self._set_tcp_user_timeout(connection._impl.socket)
+
+                self._connection_host_status_list[host_index][
+                    self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME
+                ] = cur_time
+
+                return connection
+            finally:
+                self._connection_host_status_list[host_index][
+                    self.HOST_CONNECTION_LAST_TRY_TIME
+                ] = cur_time
+
+    @staticmethod
+    def declare_queue_binding_by_channel(channel, exchange, queue, routing_key,
+                                         exchange_type, queue_expiration,
+                                         queue_auto_delete, durable):
+        channel.exchange_declare(
+            exchange, exchange_type, auto_delete=True, durable=durable
+        )
+        arguments = {}
+
+        if queue_expiration > 0:
+            arguments['x-expires'] = queue_expiration * 1000
+
+        channel.queue_declare(
+            queue, auto_delete=queue_auto_delete, durable=durable,
+            arguments=arguments
+        )
+
+        channel.queue_bind(queue, exchange, routing_key)
+
+    def declare_queue_binding(self, exchange, queue, routing_key,
+                              exchange_type, queue_expiration,
+                              queue_auto_delete, durable,
+                              timeout=None):
+        if timeout is not None and timeout < 0:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired."
+            )
+        try:
+            with self.connection_pool.acquire(timeout=timeout) as conn:
+                self.declare_queue_binding_by_channel(
+                    conn.channel, exchange, queue, routing_key, exchange_type,
+                    queue_expiration, queue_auto_delete, durable
+                )
+        except pika_pool.Timeout as e:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired. {}.".format(str(e))
+            )
+        except pika_pool.Connection.connectivity_errors as e:
+            raise pika_drv_exc.ConnectionException(
+                "Connectivity problem detected during declaring queue "
+                "binding: exchange:{}, queue: {}, routing_key: {}, "
+                "exchange_type: {}, queue_expiration: {}, queue_auto_delete: "
+                "{}, durable: {}. {}".format(
+                    exchange, queue, routing_key, exchange_type,
+                    queue_expiration, queue_auto_delete, durable, str(e)
+                )
+            )
+
+    @staticmethod
+    def _do_publish(pool, exchange, routing_key, body, properties,
+                    mandatory, expiration_time):
+        timeout = (None if expiration_time is None else
+                   expiration_time - time.time())
+        if timeout is not None and timeout < 0:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired."
+            )
+        try:
+            with pool.acquire(timeout=timeout) as conn:
+                if timeout is not None:
+                    properties.expiration = str(int(timeout * 1000))
+                conn.channel.publish(
+                    exchange=exchange,
+                    routing_key=routing_key,
+                    body=body,
+                    properties=properties,
+                    mandatory=mandatory
+                )
+        except pika_exceptions.NackError as e:
+            raise pika_drv_exc.MessageRejectedException(
+                "Can not send message: [body: {}], properties: {}] to "
+                "target [exchange: {}, routing_key: {}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except pika_exceptions.UnroutableError as e:
+            raise pika_drv_exc.RoutingException(
+                "Can not deliver message:[body:{}, properties: {}] to any"
+                "queue using target: [exchange:{}, "
+                "routing_key:{}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except pika_pool.Timeout as e:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired. {}".format(str(e))
+            )
+        except pika_pool.Connection.connectivity_errors as e:
+            if (isinstance(e, pika_exceptions.ChannelClosed)
+                    and e.args and e.args[0] == 404):
+                raise pika_drv_exc.ExchangeNotFoundException(
+                    "Attempt to send message to not existing exchange "
+                    "detected, message: [body:{}, properties: {}], target: "
+                    "[exchange:{}, routing_key:{}]. {}".format(
+                        body, properties, exchange, routing_key, str(e)
+                    )
+                )
+
+            raise pika_drv_exc.ConnectionException(
+                "Connectivity problem detected during sending the message: "
+                "[body:{}, properties: {}] to target: [exchange:{}, "
+                "routing_key:{}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except socket.timeout:
+            raise pika_drv_exc.TimeoutConnectionException(
+                "Socket timeout exceeded."
+            )
+
+    def publish(self, exchange, routing_key, body, properties, confirm,
+                mandatory, expiration_time, retrier):
+        pool = (self.connection_with_confirmation_pool if confirm else
+                self.connection_pool)
+
+        LOG.debug(
+            "Sending message:[body:{}; properties: {}] to target: "
+            "[exchange:{}; routing_key:{}]".format(
+                body, properties, exchange, routing_key
+            )
+        )
+
+        do_publish = (self._do_publish if retrier is None else
+                      retrier(self._do_publish))
+
+        return do_publish(pool, exchange, routing_key, body, properties,
+                          mandatory, expiration_time)
+
+    def get_reply_q(self, timeout=None):
+        if self._reply_consumer_enabled:
+            return self._reply_queue
+
+        with self._reply_consumer_lock:
+            if self._reply_consumer_enabled:
+                return self._reply_queue
+
+            if self._reply_queue is None:
+                self._reply_queue = "reply.{}.{}.{}".format(
+                    self.conf.project, self.conf.prog, uuid.uuid4().hex
+                )
+
+            if self._reply_listener is None:
+                self._reply_listener = pika_listener.RpcReplyPikaListener(
+                    pika_engine=self,
+                    exchange=self.rpc_reply_exchange,
+                    queue=self._reply_queue,
+                    no_ack=not self.rpc_reply_listener_ack,
+                    prefetch_count=self.rpc_reply_listener_prefetch_count
+                )
+
+                self._reply_listener.start(timeout=timeout)
+
+            if self._puller_thread is None:
+                self._puller_thread = threading.Thread(target=self._poller)
+                self._puller_thread.daemon = True
+
+            if not self._puller_thread.is_alive():
+                self._puller_thread.start()
+
+            self._reply_consumer_enabled = True
+
+        return self._reply_queue
+
+    def _poller(self):
+        while self._reply_consumer_thread_run_flag:
+            try:
+                message = self._reply_listener.poll(timeout=1)
+                message.acknowledge()
+                if message is None:
+                    continue
+                i = 0
+                curtime = time.time()
+                while (i < len(self._reply_waiting_future_list) and
+                        self._reply_consumer_thread_run_flag):
+                    msg_id, future, expiration = (
+                        self._reply_waiting_future_list[i]
+                    )
+                    if expiration and expiration < curtime:
+                        del self._reply_waiting_future_list[i]
+                    elif msg_id == message.msg_id:
+                        del self._reply_waiting_future_list[i]
+                        future.set_result(message)
+                    else:
+                        i += 1
+            except BaseException:
+                LOG.exception("Exception during reply polling")
+
+    def register_reply_waiter(self, msg_id, future, expiration_time):
+        self._reply_waiting_future_list.append(
+            (msg_id, future, expiration_time)
+        )
+
+    def cleanup(self):
+        with self._reply_consumer_lock:
+            self._reply_consumer_enabled = False
+
+            if self._puller_thread:
+                if self._puller_thread.is_alive():
+                    self._reply_consumer_thread_run_flag = False
+                    self._puller_thread.join()
+                self._puller_thread = None
+
+            if self._reply_listener:
+                self._reply_listener.stop()
+                self._reply_listener.cleanup()
+                self._reply_listener = None
+
+                self._reply_queue = None
diff --git a/oslo_messaging/_drivers/pika_driver/pika_exceptions.py b/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
new file mode 100644
index 000000000..62e370fa3
--- /dev/null
+++ b/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
@@ -0,0 +1,43 @@
+#    Copyright 2015 Mirantis, 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.
+
+from oslo_messaging import exceptions
+
+
+class ExchangeNotFoundException(exceptions.MessageDeliveryFailure):
+    pass
+
+
+class MessageRejectedException(exceptions.MessageDeliveryFailure):
+    pass
+
+
+class RoutingException(exceptions.MessageDeliveryFailure):
+    pass
+
+
+class ConnectionException(exceptions.MessagingException):
+    pass
+
+
+class HostConnectionNotAllowedException(ConnectionException):
+    pass
+
+
+class EstablishConnectionException(ConnectionException):
+    pass
+
+
+class TimeoutConnectionException(ConnectionException):
+    pass
diff --git a/oslo_messaging/_drivers/pika_driver/pika_listener.py b/oslo_messaging/_drivers/pika_driver/pika_listener.py
new file mode 100644
index 000000000..08d17ea24
--- /dev/null
+++ b/oslo_messaging/_drivers/pika_driver/pika_listener.py
@@ -0,0 +1,266 @@
+#    Copyright 2015 Mirantis, 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 collections
+
+from oslo_log import log as logging
+
+import threading
+import time
+
+from oslo_messaging._drivers.pika_driver import pika_message
+import pika_pool
+import retrying
+
+LOG = logging.getLogger(__name__)
+
+
+class PikaListener(object):
+    def __init__(self, pika_engine, no_ack, prefetch_count):
+        self._pika_engine = pika_engine
+
+        self._connection = None
+        self._channel = None
+        self._lock = threading.Lock()
+
+        self._prefetch_count = prefetch_count
+        self._no_ack = no_ack
+
+        self._started = False
+
+        self._message_queue = collections.deque()
+
+    def _reconnect(self):
+        self._connection = self._pika_engine.create_connection(
+            for_listening=True
+        )
+        self._channel = self._connection.channel()
+        self._channel.basic_qos(prefetch_count=self._prefetch_count)
+
+        self._on_reconnected()
+
+    def _on_reconnected(self):
+        raise NotImplementedError(
+            "It is base class. Please declare consumers here"
+        )
+
+    def _start_consuming(self, queue):
+        self._channel.basic_consume(self._on_message_callback,
+                                    queue, no_ack=self._no_ack)
+
+    def _on_message_callback(self, unused, method, properties, body):
+        self._message_queue.append((self._channel, method, properties, body))
+
+    def _cleanup(self):
+        if self._channel:
+            try:
+                self._channel.close()
+            except Exception as ex:
+                if not pika_pool.Connection.is_connection_invalidated(ex):
+                    LOG.exception("Unexpected error during closing channel")
+            self._channel = None
+
+        if self._connection:
+            try:
+                self._connection.close()
+            except Exception as ex:
+                if not pika_pool.Connection.is_connection_invalidated(ex):
+                    LOG.exception("Unexpected error during closing connection")
+            self._connection = None
+
+    def poll(self, timeout=None):
+        start = time.time()
+        while not self._message_queue:
+            with self._lock:
+                if not self._started:
+                    return None
+
+                try:
+                    if self._channel is None:
+                        self._reconnect()
+                    self._connection.process_data_events()
+                except Exception:
+                    self._cleanup()
+                    raise
+            if timeout and time.time() - start > timeout:
+                return None
+
+        return self._message_queue.popleft()
+
+    def start(self):
+        self._started = True
+
+    def stop(self):
+        with self._lock:
+            if not self._started:
+                return
+
+            self._started = False
+            self._cleanup()
+
+    def reconnect(self):
+        with self._lock:
+            self._cleanup()
+            try:
+                self._reconnect()
+            except Exception:
+                self._cleanup()
+                raise
+
+    def cleanup(self):
+        with self._lock:
+            self._cleanup()
+
+
+class RpcServicePikaListener(PikaListener):
+    def __init__(self, pika_engine, target, no_ack, prefetch_count):
+        self._target = target
+
+        super(RpcServicePikaListener, self).__init__(
+            pika_engine, no_ack=no_ack, prefetch_count=prefetch_count)
+
+    def _on_reconnected(self):
+        exchange = (self._target.exchange or
+                    self._pika_engine.default_rpc_exchange)
+        queue = '{}'.format(self._target.topic)
+        server_queue = '{}.{}'.format(queue, self._target.server)
+
+        fanout_exchange = '{}_fanout_{}'.format(
+            self._pika_engine.default_rpc_exchange, self._target.topic
+        )
+
+        queue_expiration = (
+            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
+            exchange=exchange, queue=queue, routing_key=queue,
+            exchange_type='direct', queue_expiration=queue_expiration,
+            queue_auto_delete=False, durable=False
+        )
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
+            exchange=exchange, queue=server_queue, routing_key=server_queue,
+            exchange_type='direct', queue_expiration=queue_expiration,
+            queue_auto_delete=False, durable=False
+        )
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
+            exchange=fanout_exchange, queue=server_queue, routing_key="",
+            exchange_type='fanout', queue_expiration=queue_expiration,
+            queue_auto_delete=False, durable=False
+        )
+
+        self._start_consuming(queue)
+        self._start_consuming(server_queue)
+
+    def poll(self, timeout=None):
+        msg = super(RpcServicePikaListener, self).poll(timeout)
+        if msg is None:
+            return None
+        return pika_message.PikaIncomingMessage(
+            self._pika_engine, *msg, no_ack=self._no_ack
+        )
+
+
+class RpcReplyPikaListener(PikaListener):
+    def __init__(self, pika_engine, exchange, queue, no_ack, prefetch_count):
+        self._exchange = exchange
+        self._queue = queue
+
+        super(RpcReplyPikaListener, self).__init__(
+            pika_engine, no_ack, prefetch_count
+        )
+
+    def _on_reconnected(self):
+        queue_expiration = (
+            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
+            exchange=self._exchange, queue=self._queue,
+            routing_key=self._queue, exchange_type='direct',
+            queue_expiration=queue_expiration, queue_auto_delete=False,
+            durable=False
+        )
+        self._start_consuming(self._queue)
+
+    def start(self, timeout=None):
+        super(RpcReplyPikaListener, self).start()
+
+        def on_exception(ex):
+            LOG.warn(str(ex))
+
+            return True
+
+        retrier = retrying.retry(
+            stop_max_attempt_number=self._pika_engine.rpc_reply_retry_attempts,
+            stop_max_delay=timeout * 1000,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
+            retry_on_exception=on_exception,
+        )
+
+        retrier(self.reconnect)()
+
+    def poll(self, timeout=None):
+        msg = super(RpcReplyPikaListener, self).poll(timeout)
+        if msg is None:
+            return None
+        return pika_message.PikaIncomingMessage(
+            self._pika_engine, *msg, no_ack=self._no_ack
+        )
+
+
+class NotificationPikaListener(PikaListener):
+    def __init__(self, pika_engine, targets_and_priorities,
+                 queue_name=None, prefetch_count=100):
+        self._targets_and_priorities = targets_and_priorities
+        self._queue_name = queue_name
+
+        super(NotificationPikaListener, self).__init__(
+            pika_engine, no_ack=False, prefetch_count=prefetch_count
+        )
+
+    def _on_reconnected(self):
+        queues_to_consume = set()
+        for target, priority in self._targets_and_priorities:
+            routing_key = '%s.%s' % (target.topic, priority)
+            queue = self._queue_name or routing_key
+            self._pika_engine.declare_queue_binding_by_channel(
+                channel=self._channel,
+                exchange=(
+                    target.exchange or
+                    self._pika_engine.default_notification_exchange
+                ),
+                queue = queue,
+                routing_key=routing_key,
+                exchange_type='direct',
+                queue_expiration=None,
+                queue_auto_delete=False,
+                durable=self._pika_engine.notification_persistence,
+            )
+            queues_to_consume.add(queue)
+
+        for queue_to_consume in queues_to_consume:
+            self._start_consuming(queue_to_consume)
+
+    def poll(self, timeout=None):
+        msg = super(NotificationPikaListener, self).poll(timeout)
+        if msg is None:
+            return None
+        return pika_message.PikaIncomingMessage(
+            self._pika_engine, *msg, no_ack=self._no_ack
+        )
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
new file mode 100644
index 000000000..09559b8a0
--- /dev/null
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -0,0 +1,217 @@
+#    Copyright 2015 Mirantis, 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.
+from concurrent import futures
+
+from oslo_log import log as logging
+
+from oslo_messaging._drivers import common
+from oslo_messaging import exceptions
+
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
+
+from oslo_serialization import jsonutils
+
+from pika import spec as pika_spec
+
+import retrying
+import six
+import time
+import uuid
+
+LOG = logging.getLogger(__name__)
+
+
+class PikaIncomingMessage(object):
+
+    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        self._pika_engine = pika_engine
+        self._no_ack = no_ack
+        self._channel = channel
+        self.delivery_tag = method.delivery_tag
+
+        self.content_type = getattr(properties, "content_type",
+                                    "application/json")
+        self.content_encoding = getattr(properties, "content_encoding",
+                                        "utf-8")
+
+        self.expiration_time = (
+            None if properties.expiration is None else
+            time.time() + int(properties.expiration) / 1000
+        )
+
+        if self.content_type != "application/json":
+            raise NotImplementedError("Content-type['{}'] is not valid, "
+                                      "'application/json' only is supported.")
+
+        message_dict = common.deserialize_msg(
+            jsonutils.loads(body, encoding=self.content_encoding)
+        )
+
+        self.unique_id = message_dict.pop('_unique_id')
+        self.msg_id = message_dict.pop('_msg_id', None)
+        self.reply_q = message_dict.pop('_reply_q', None)
+
+        context_dict = {}
+
+        for key in list(message_dict.keys()):
+            key = six.text_type(key)
+            if key.startswith('_context_'):
+                value = message_dict.pop(key)
+                context_dict[key[9:]] = value
+
+        self.message = message_dict
+        self.ctxt = context_dict
+
+    def reply(self, reply=None, failure=None, log_failure=True):
+        if not (self.msg_id and self.reply_q):
+            return
+
+        if failure:
+            failure = common.serialize_remote_exception(failure, log_failure)
+
+        msg = {
+            'result': reply,
+            'failure': failure,
+            '_unique_id': uuid.uuid4().hex,
+            '_msg_id': self.msg_id,
+            'ending': True
+        }
+
+        def on_exception(ex):
+            if isinstance(ex, pika_drv_exc.ConnectionException):
+                LOG.warn(str(ex))
+                return True
+            else:
+                return False
+
+        retrier = retrying.retry(
+            stop_max_attempt_number=(
+                None if self._pika_engine.rpc_reply_retry_attempts == -1
+                else self._pika_engine.rpc_reply_retry_attempts
+            ),
+            retry_on_exception=on_exception,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
+        )
+
+        try:
+            self._pika_engine.publish(
+                exchange=self._pika_engine.rpc_reply_exchange,
+                routing_key=self.reply_q,
+                body=jsonutils.dumps(
+                    common.serialize_msg(msg),
+                    encoding=self.content_encoding
+                ),
+                properties=pika_spec.BasicProperties(
+                    content_encoding=self.content_encoding,
+                    content_type=self.content_type,
+                ),
+                confirm=True,
+                mandatory=False,
+                expiration_time=self.expiration_time,
+                retrier=retrier
+            )
+            LOG.debug(
+                "Message [id:'{}'] replied to '{}'.".format(
+                    self.msg_id, self.reply_q
+                )
+            )
+        except Exception:
+            LOG.exception(
+                "Message [id:'{}'] wasn't replied to : {}".format(
+                    self.msg_id, self.reply_q
+                )
+            )
+
+    def acknowledge(self):
+        if not self._no_ack:
+            self._channel.basic_ack(delivery_tag=self.delivery_tag)
+
+    def requeue(self):
+        if not self._no_ack:
+            return self._channel.basic_nack(delivery_tag=self.delivery_tag,
+                                            requeue=True)
+
+
+class PikaOutgoingMessage(object):
+
+    def __init__(self, pika_engine, message, context,
+                 content_type="application/json", content_encoding="utf-8"):
+        self._pika_engine = pika_engine
+
+        self.content_type = content_type
+        self.content_encoding = content_encoding
+
+        if self.content_type != "application/json":
+            raise NotImplementedError("Content-type['{}'] is not valid, "
+                                      "'application/json' only is supported.")
+
+        self.message = message
+        self.context = context
+
+        self.unique_id = uuid.uuid4().hex
+        self.msg_id = None
+
+    def send(self, exchange, routing_key='', confirm=True,
+             wait_for_reply=False, mandatory=True, persistent=False,
+             timeout=None, retrier=None):
+        msg = self.message.copy()
+
+        msg['_unique_id'] = self.unique_id
+
+        for key, value in self.context.iteritems():
+            key = six.text_type(key)
+            msg['_context_' + key] = value
+
+        properties = pika_spec.BasicProperties(
+            content_encoding=self.content_encoding,
+            content_type=self.content_type,
+            delivery_mode=2 if persistent else 1
+        )
+
+        expiration_time = (
+            None if timeout is None else (timeout + time.time())
+        )
+
+        if wait_for_reply:
+            self.msg_id = uuid.uuid4().hex
+            msg['_msg_id'] = self.msg_id
+            LOG.debug('MSG_ID is %s', self.msg_id)
+
+            msg['_reply_q'] = self._pika_engine.get_reply_q(timeout)
+
+            future = futures.Future()
+
+            self._pika_engine.register_reply_waiter(
+                msg_id=self.msg_id, future=future,
+                expiration_time=expiration_time
+            )
+
+        self._pika_engine.publish(
+            exchange=exchange, routing_key=routing_key,
+            body=jsonutils.dumps(
+                common.serialize_msg(msg),
+                encoding=self.content_encoding
+            ),
+            properties=properties,
+            confirm=confirm,
+            mandatory=mandatory,
+            expiration_time=expiration_time,
+            retrier=retrier
+        )
+
+        if wait_for_reply:
+            try:
+                return future.result(timeout)
+            except futures.TimeoutError:
+                raise exceptions.MessagingTimeout()

From cc3db22c6a888274c9daf73dda9d64f23bf2d030 Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Wed, 11 Nov 2015 18:54:37 +0200
Subject: [PATCH 06/16] Implements more smart retrying

This patch makes difference between messages which are sent
with retry=0 and retry!=0. In first case we consider that
request could be not idempotent and we should guarantee
that such messages we be not sent more then one time.
Therefore we use consuming without acknowledgements for such
messages and consuming with acknowledgements for messages which
could be processed twice to avoid message loses

Change-Id: I7dc1705e84dc6a7671bf1f379d4ef4980e41491e
---
 oslo_messaging/_drivers/impl_pika.py          |  65 ++--
 .../_drivers/pika_driver/pika_engine.py       | 194 +----------
 .../_drivers/pika_driver/pika_listener.py     | 321 +++++-------------
 .../_drivers/pika_driver/pika_message.py      | 223 +++++++++---
 .../_drivers/pika_driver/pika_poller.py       | 293 ++++++++++++++++
 5 files changed, 588 insertions(+), 508 deletions(-)
 create mode 100644 oslo_messaging/_drivers/pika_driver/pika_poller.py

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index 085901a16..b4b6d4288 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -13,17 +13,19 @@
 #    under the License.
 
 import retrying
-
 import sys
+import time
 
 from oslo_config import cfg
 from oslo_log import log as logging
 
 from oslo_messaging._drivers import common
-from oslo_messaging._drivers.pika_driver import pika_engine
+
+from oslo_messaging._drivers.pika_driver import pika_engine as pika_drv_engine
 from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
-from oslo_messaging._drivers.pika_driver import pika_listener
-from oslo_messaging._drivers.pika_driver import pika_message
+from oslo_messaging._drivers.pika_driver import pika_listener as pika_drv_lstnr
+from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
+from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
 
 from oslo_messaging import exceptions
 
@@ -95,15 +97,6 @@ rpc_opts = [
                help="Exchange name for for sending RPC messages"),
     cfg.StrOpt('rpc_reply_exchange', default="${control_exchange}_rpc_reply",
                help="Exchange name for for receiving RPC replies"),
-
-    cfg.BoolOpt('rpc_listener_ack', default=True,
-                help="Disable to increase performance. If disabled - some "
-                     "messages may be lost in case of connectivity problem. "
-                     "If enabled - may cause not needed message redelivery "
-                     "and rpc request could be processed more then one time"),
-    cfg.BoolOpt('rpc_reply_listener_ack', default=True,
-                help="Disable to increase performance. If disabled - some "
-                     "replies may be lost in case of connectivity problem."),
     cfg.IntOpt(
         'rpc_listener_prefetch_count', default=10,
         help="Max number of not acknowledged message which RabbitMQ can send "
@@ -176,13 +169,19 @@ class PikaDriver(object):
         self.conf = conf
         self._allowed_remote_exmods = allowed_remote_exmods
 
-        self._pika_engine = pika_engine.PikaEngine(conf, url, default_exchange)
+        self._pika_engine = pika_drv_engine.PikaEngine(
+            conf, url, default_exchange
+        )
+        self._reply_listener = pika_drv_lstnr.RpcReplyPikaListener(
+            self._pika_engine
+        )
 
     def require_features(self, requeue=False):
         pass
 
     def send(self, target, ctxt, message, wait_for_reply=None, timeout=None,
              retry=None):
+        expiration_time = None if timeout is None else time.time() + timeout
 
         if retry is None:
             retry = self._pika_engine.default_rpc_retry_attempts
@@ -204,29 +203,12 @@ class PikaDriver(object):
             )
         )
 
-        msg = pika_message.PikaOutgoingMessage(self._pika_engine, message,
-                                               ctxt)
-
-        if target.fanout:
-            return msg.send(
-                exchange='{}_fanout_{}'.format(
-                    self._pika_engine.default_rpc_exchange, target.topic
-                ),
-                timeout=timeout, confirm=True, mandatory=False,
-                retrier=retrier
-            )
-
-        queue = target.topic
-        if target.server:
-            queue = '{}.{}'.format(queue, target.server)
-
+        msg = pika_drv_msg.RpcPikaOutgoingMessage(self._pika_engine, message,
+                                                  ctxt)
         reply = msg.send(
-            exchange=target.exchange or self._pika_engine.default_rpc_exchange,
-            routing_key=queue,
-            wait_for_reply=wait_for_reply,
-            timeout=timeout,
-            confirm=True,
-            mandatory=True,
+            target,
+            reply_listener=self._reply_listener if wait_for_reply else None,
+            expiration_time=expiration_time,
             retrier=retrier
         )
 
@@ -276,16 +258,14 @@ class PikaDriver(object):
             wait_fixed=self._pika_engine.notification_retry_delay * 1000,
         )
 
-        msg = pika_message.PikaOutgoingMessage(self._pika_engine, message,
+        msg = pika_drv_msg.PikaOutgoingMessage(self._pika_engine, message,
                                                ctxt)
-
         return msg.send(
             exchange=(
                 target.exchange or
                 self._pika_engine.default_notification_exchange
             ),
             routing_key=target.topic,
-            wait_for_reply=False,
             confirm=True,
             mandatory=True,
             persistent=self._pika_engine.notification_persistence,
@@ -293,23 +273,22 @@ class PikaDriver(object):
         )
 
     def listen(self, target):
-        listener = pika_listener.RpcServicePikaListener(
+        listener = pika_drv_poller.RpcServicePikaPoller(
             self._pika_engine, target,
-            no_ack=not self._pika_engine.rpc_listener_ack,
             prefetch_count=self._pika_engine.rpc_listener_prefetch_count
         )
         listener.start()
         return listener
 
     def listen_for_notifications(self, targets_and_priorities, pool):
-        listener = pika_listener.NotificationPikaListener(
+        listener = pika_drv_poller.NotificationPikaPoller(
             self._pika_engine, targets_and_priorities, pool
         )
         listener.start()
         return listener
 
     def cleanup(self):
-        self._pika_engine.cleanup()
+        self._reply_listener.cleanup()
 
 
 class PikaDriverCompatibleWithRabbitDriver(PikaDriver):
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 991824d64..03ccccdf0 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -18,18 +18,15 @@ from oslo_log import log as logging
 from oslo_messaging import exceptions
 
 from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
-from oslo_messaging._drivers.pika_driver import pika_listener
 
 import pika
 from pika import adapters as pika_adapters
 from pika import credentials as pika_credentials
-from pika import exceptions as pika_exceptions
 
 import pika_pool
 
 import threading
 import time
-import uuid
 
 LOG = logging.getLogger(__name__)
 
@@ -65,12 +62,6 @@ class PikaEngine(object):
             default_exchange
         )
 
-        self.rpc_listener_ack = conf.oslo_messaging_pika.rpc_listener_ack
-
-        self.rpc_reply_listener_ack = (
-            conf.oslo_messaging_pika.rpc_reply_listener_ack
-        )
-
         self.rpc_listener_prefetch_count = (
             conf.oslo_messaging_pika.rpc_listener_prefetch_count
         )
@@ -129,17 +120,6 @@ class PikaEngine(object):
             raise ValueError("notification_retry_delay should be non-negative "
                              "integer")
 
-        # preparing poller for listening replies
-        self._reply_queue = None
-
-        self._reply_listener = None
-        self._reply_waiting_future_list = []
-
-        self._reply_consumer_enabled = False
-        self._reply_consumer_thread_run_flag = True
-        self._reply_consumer_lock = threading.Lock()
-        self._puller_thread = None
-
         self._tcp_user_timeout = self.conf.oslo_messaging_pika.tcp_user_timeout
         self._host_connection_reconnect_delay = (
             self.conf.oslo_messaging_pika.host_connection_reconnect_delay
@@ -348,163 +328,19 @@ class PikaEngine(object):
                 )
             )
 
+    def get_rpc_exchange_name(self, exchange, topic, fanout, no_ack):
+        exchange = (exchange or self.default_rpc_exchange)
+
+        if fanout:
+            exchange = '{}_fanout_{}_{}'.format(
+                exchange, "no_ack" if no_ack else "with_ack", topic
+            )
+        return exchange
+
     @staticmethod
-    def _do_publish(pool, exchange, routing_key, body, properties,
-                    mandatory, expiration_time):
-        timeout = (None if expiration_time is None else
-                   expiration_time - time.time())
-        if timeout is not None and timeout < 0:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired."
-            )
-        try:
-            with pool.acquire(timeout=timeout) as conn:
-                if timeout is not None:
-                    properties.expiration = str(int(timeout * 1000))
-                conn.channel.publish(
-                    exchange=exchange,
-                    routing_key=routing_key,
-                    body=body,
-                    properties=properties,
-                    mandatory=mandatory
-                )
-        except pika_exceptions.NackError as e:
-            raise pika_drv_exc.MessageRejectedException(
-                "Can not send message: [body: {}], properties: {}] to "
-                "target [exchange: {}, routing_key: {}]. {}".format(
-                    body, properties, exchange, routing_key, str(e)
-                )
-            )
-        except pika_exceptions.UnroutableError as e:
-            raise pika_drv_exc.RoutingException(
-                "Can not deliver message:[body:{}, properties: {}] to any"
-                "queue using target: [exchange:{}, "
-                "routing_key:{}]. {}".format(
-                    body, properties, exchange, routing_key, str(e)
-                )
-            )
-        except pika_pool.Timeout as e:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired. {}".format(str(e))
-            )
-        except pika_pool.Connection.connectivity_errors as e:
-            if (isinstance(e, pika_exceptions.ChannelClosed)
-                    and e.args and e.args[0] == 404):
-                raise pika_drv_exc.ExchangeNotFoundException(
-                    "Attempt to send message to not existing exchange "
-                    "detected, message: [body:{}, properties: {}], target: "
-                    "[exchange:{}, routing_key:{}]. {}".format(
-                        body, properties, exchange, routing_key, str(e)
-                    )
-                )
-
-            raise pika_drv_exc.ConnectionException(
-                "Connectivity problem detected during sending the message: "
-                "[body:{}, properties: {}] to target: [exchange:{}, "
-                "routing_key:{}]. {}".format(
-                    body, properties, exchange, routing_key, str(e)
-                )
-            )
-        except socket.timeout:
-            raise pika_drv_exc.TimeoutConnectionException(
-                "Socket timeout exceeded."
-            )
-
-    def publish(self, exchange, routing_key, body, properties, confirm,
-                mandatory, expiration_time, retrier):
-        pool = (self.connection_with_confirmation_pool if confirm else
-                self.connection_pool)
-
-        LOG.debug(
-            "Sending message:[body:{}; properties: {}] to target: "
-            "[exchange:{}; routing_key:{}]".format(
-                body, properties, exchange, routing_key
-            )
-        )
-
-        do_publish = (self._do_publish if retrier is None else
-                      retrier(self._do_publish))
-
-        return do_publish(pool, exchange, routing_key, body, properties,
-                          mandatory, expiration_time)
-
-    def get_reply_q(self, timeout=None):
-        if self._reply_consumer_enabled:
-            return self._reply_queue
-
-        with self._reply_consumer_lock:
-            if self._reply_consumer_enabled:
-                return self._reply_queue
-
-            if self._reply_queue is None:
-                self._reply_queue = "reply.{}.{}.{}".format(
-                    self.conf.project, self.conf.prog, uuid.uuid4().hex
-                )
-
-            if self._reply_listener is None:
-                self._reply_listener = pika_listener.RpcReplyPikaListener(
-                    pika_engine=self,
-                    exchange=self.rpc_reply_exchange,
-                    queue=self._reply_queue,
-                    no_ack=not self.rpc_reply_listener_ack,
-                    prefetch_count=self.rpc_reply_listener_prefetch_count
-                )
-
-                self._reply_listener.start(timeout=timeout)
-
-            if self._puller_thread is None:
-                self._puller_thread = threading.Thread(target=self._poller)
-                self._puller_thread.daemon = True
-
-            if not self._puller_thread.is_alive():
-                self._puller_thread.start()
-
-            self._reply_consumer_enabled = True
-
-        return self._reply_queue
-
-    def _poller(self):
-        while self._reply_consumer_thread_run_flag:
-            try:
-                message = self._reply_listener.poll(timeout=1)
-                message.acknowledge()
-                if message is None:
-                    continue
-                i = 0
-                curtime = time.time()
-                while (i < len(self._reply_waiting_future_list) and
-                        self._reply_consumer_thread_run_flag):
-                    msg_id, future, expiration = (
-                        self._reply_waiting_future_list[i]
-                    )
-                    if expiration and expiration < curtime:
-                        del self._reply_waiting_future_list[i]
-                    elif msg_id == message.msg_id:
-                        del self._reply_waiting_future_list[i]
-                        future.set_result(message)
-                    else:
-                        i += 1
-            except BaseException:
-                LOG.exception("Exception during reply polling")
-
-    def register_reply_waiter(self, msg_id, future, expiration_time):
-        self._reply_waiting_future_list.append(
-            (msg_id, future, expiration_time)
-        )
-
-    def cleanup(self):
-        with self._reply_consumer_lock:
-            self._reply_consumer_enabled = False
-
-            if self._puller_thread:
-                if self._puller_thread.is_alive():
-                    self._reply_consumer_thread_run_flag = False
-                    self._puller_thread.join()
-                self._puller_thread = None
-
-            if self._reply_listener:
-                self._reply_listener.stop()
-                self._reply_listener.cleanup()
-                self._reply_listener = None
-
-                self._reply_queue = None
+    def get_rpc_queue_name(topic, server, no_ack):
+        queue_parts = ["no_ack" if no_ack else "with_ack", topic]
+        if server is not None:
+            queue_parts.append(server)
+        queue = '.'.join(queue_parts)
+        return queue
diff --git a/oslo_messaging/_drivers/pika_driver/pika_listener.py b/oslo_messaging/_drivers/pika_driver/pika_listener.py
index 08d17ea24..493adf1ea 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_listener.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_listener.py
@@ -12,255 +12,112 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import collections
-
 from oslo_log import log as logging
 
+from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
+
+
 import threading
 import time
-
-from oslo_messaging._drivers.pika_driver import pika_message
-import pika_pool
-import retrying
+import uuid
 
 LOG = logging.getLogger(__name__)
 
 
-class PikaListener(object):
-    def __init__(self, pika_engine, no_ack, prefetch_count):
+class RpcReplyPikaListener(object):
+    def __init__(self, pika_engine):
         self._pika_engine = pika_engine
 
-        self._connection = None
-        self._channel = None
-        self._lock = threading.Lock()
+        # preparing poller for listening replies
+        self._reply_queue = None
 
-        self._prefetch_count = prefetch_count
-        self._no_ack = no_ack
+        self._reply_poller = None
+        self._reply_waiting_future_list = []
 
-        self._started = False
+        self._reply_consumer_enabled = False
+        self._reply_consumer_thread_run_flag = True
+        self._reply_consumer_lock = threading.Lock()
+        self._puller_thread = None
 
-        self._message_queue = collections.deque()
+    def get_reply_qname(self, expiration_time=None):
+        if self._reply_consumer_enabled:
+            return self._reply_queue
 
-    def _reconnect(self):
-        self._connection = self._pika_engine.create_connection(
-            for_listening=True
+        with self._reply_consumer_lock:
+            if self._reply_consumer_enabled:
+                return self._reply_queue
+
+            if self._reply_queue is None:
+                self._reply_queue = "reply.{}.{}.{}".format(
+                    self._pika_engine.conf.project,
+                    self._pika_engine.conf.prog, uuid.uuid4().hex
+                )
+
+            if self._reply_poller is None:
+                self._reply_poller = pika_drv_poller.RpcReplyPikaPoller(
+                    pika_engine=self._pika_engine,
+                    exchange=self._pika_engine.rpc_reply_exchange,
+                    queue=self._reply_queue,
+                    prefetch_count=(
+                        self._pika_engine.rpc_reply_listener_prefetch_count
+                    )
+                )
+
+                self._reply_poller.start(timeout=expiration_time - time.time())
+
+            if self._puller_thread is None:
+                self._puller_thread = threading.Thread(target=self._poller)
+                self._puller_thread.daemon = True
+
+            if not self._puller_thread.is_alive():
+                self._puller_thread.start()
+
+            self._reply_consumer_enabled = True
+
+        return self._reply_queue
+
+    def _poller(self):
+        while self._reply_consumer_thread_run_flag:
+            try:
+                message = self._reply_poller.poll(timeout=1)
+                if message is None:
+                    continue
+                message.acknowledge()
+                i = 0
+                curtime = time.time()
+                while (i < len(self._reply_waiting_future_list) and
+                        self._reply_consumer_thread_run_flag):
+                    msg_id, future, expiration = (
+                        self._reply_waiting_future_list[i]
+                    )
+                    if expiration and expiration < curtime:
+                        del self._reply_waiting_future_list[i]
+                    elif msg_id == message.msg_id:
+                        del self._reply_waiting_future_list[i]
+                        future.set_result(message)
+                    else:
+                        i += 1
+            except BaseException:
+                LOG.exception("Exception during reply polling")
+
+    def register_reply_waiter(self, msg_id, future, expiration_time):
+        self._reply_waiting_future_list.append(
+            (msg_id, future, expiration_time)
         )
-        self._channel = self._connection.channel()
-        self._channel.basic_qos(prefetch_count=self._prefetch_count)
-
-        self._on_reconnected()
-
-    def _on_reconnected(self):
-        raise NotImplementedError(
-            "It is base class. Please declare consumers here"
-        )
-
-    def _start_consuming(self, queue):
-        self._channel.basic_consume(self._on_message_callback,
-                                    queue, no_ack=self._no_ack)
-
-    def _on_message_callback(self, unused, method, properties, body):
-        self._message_queue.append((self._channel, method, properties, body))
-
-    def _cleanup(self):
-        if self._channel:
-            try:
-                self._channel.close()
-            except Exception as ex:
-                if not pika_pool.Connection.is_connection_invalidated(ex):
-                    LOG.exception("Unexpected error during closing channel")
-            self._channel = None
-
-        if self._connection:
-            try:
-                self._connection.close()
-            except Exception as ex:
-                if not pika_pool.Connection.is_connection_invalidated(ex):
-                    LOG.exception("Unexpected error during closing connection")
-            self._connection = None
-
-    def poll(self, timeout=None):
-        start = time.time()
-        while not self._message_queue:
-            with self._lock:
-                if not self._started:
-                    return None
-
-                try:
-                    if self._channel is None:
-                        self._reconnect()
-                    self._connection.process_data_events()
-                except Exception:
-                    self._cleanup()
-                    raise
-            if timeout and time.time() - start > timeout:
-                return None
-
-        return self._message_queue.popleft()
-
-    def start(self):
-        self._started = True
-
-    def stop(self):
-        with self._lock:
-            if not self._started:
-                return
-
-            self._started = False
-            self._cleanup()
-
-    def reconnect(self):
-        with self._lock:
-            self._cleanup()
-            try:
-                self._reconnect()
-            except Exception:
-                self._cleanup()
-                raise
 
     def cleanup(self):
-        with self._lock:
-            self._cleanup()
+        with self._reply_consumer_lock:
+            self._reply_consumer_enabled = False
 
+            if self._puller_thread:
+                if self._puller_thread.is_alive():
+                    self._reply_consumer_thread_run_flag = False
+                    self._puller_thread.join()
+                self._puller_thread = None
 
-class RpcServicePikaListener(PikaListener):
-    def __init__(self, pika_engine, target, no_ack, prefetch_count):
-        self._target = target
+            if self._reply_poller:
+                self._reply_poller.stop()
+                self._reply_poller.cleanup()
+                self._reply_poller = None
 
-        super(RpcServicePikaListener, self).__init__(
-            pika_engine, no_ack=no_ack, prefetch_count=prefetch_count)
-
-    def _on_reconnected(self):
-        exchange = (self._target.exchange or
-                    self._pika_engine.default_rpc_exchange)
-        queue = '{}'.format(self._target.topic)
-        server_queue = '{}.{}'.format(queue, self._target.server)
-
-        fanout_exchange = '{}_fanout_{}'.format(
-            self._pika_engine.default_rpc_exchange, self._target.topic
-        )
-
-        queue_expiration = (
-            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
-        )
-
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=exchange, queue=queue, routing_key=queue,
-            exchange_type='direct', queue_expiration=queue_expiration,
-            queue_auto_delete=False, durable=False
-        )
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=exchange, queue=server_queue, routing_key=server_queue,
-            exchange_type='direct', queue_expiration=queue_expiration,
-            queue_auto_delete=False, durable=False
-        )
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=fanout_exchange, queue=server_queue, routing_key="",
-            exchange_type='fanout', queue_expiration=queue_expiration,
-            queue_auto_delete=False, durable=False
-        )
-
-        self._start_consuming(queue)
-        self._start_consuming(server_queue)
-
-    def poll(self, timeout=None):
-        msg = super(RpcServicePikaListener, self).poll(timeout)
-        if msg is None:
-            return None
-        return pika_message.PikaIncomingMessage(
-            self._pika_engine, *msg, no_ack=self._no_ack
-        )
-
-
-class RpcReplyPikaListener(PikaListener):
-    def __init__(self, pika_engine, exchange, queue, no_ack, prefetch_count):
-        self._exchange = exchange
-        self._queue = queue
-
-        super(RpcReplyPikaListener, self).__init__(
-            pika_engine, no_ack, prefetch_count
-        )
-
-    def _on_reconnected(self):
-        queue_expiration = (
-            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
-        )
-
-        self._pika_engine.declare_queue_binding_by_channel(
-            channel=self._channel,
-            exchange=self._exchange, queue=self._queue,
-            routing_key=self._queue, exchange_type='direct',
-            queue_expiration=queue_expiration, queue_auto_delete=False,
-            durable=False
-        )
-        self._start_consuming(self._queue)
-
-    def start(self, timeout=None):
-        super(RpcReplyPikaListener, self).start()
-
-        def on_exception(ex):
-            LOG.warn(str(ex))
-
-            return True
-
-        retrier = retrying.retry(
-            stop_max_attempt_number=self._pika_engine.rpc_reply_retry_attempts,
-            stop_max_delay=timeout * 1000,
-            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
-            retry_on_exception=on_exception,
-        )
-
-        retrier(self.reconnect)()
-
-    def poll(self, timeout=None):
-        msg = super(RpcReplyPikaListener, self).poll(timeout)
-        if msg is None:
-            return None
-        return pika_message.PikaIncomingMessage(
-            self._pika_engine, *msg, no_ack=self._no_ack
-        )
-
-
-class NotificationPikaListener(PikaListener):
-    def __init__(self, pika_engine, targets_and_priorities,
-                 queue_name=None, prefetch_count=100):
-        self._targets_and_priorities = targets_and_priorities
-        self._queue_name = queue_name
-
-        super(NotificationPikaListener, self).__init__(
-            pika_engine, no_ack=False, prefetch_count=prefetch_count
-        )
-
-    def _on_reconnected(self):
-        queues_to_consume = set()
-        for target, priority in self._targets_and_priorities:
-            routing_key = '%s.%s' % (target.topic, priority)
-            queue = self._queue_name or routing_key
-            self._pika_engine.declare_queue_binding_by_channel(
-                channel=self._channel,
-                exchange=(
-                    target.exchange or
-                    self._pika_engine.default_notification_exchange
-                ),
-                queue = queue,
-                routing_key=routing_key,
-                exchange_type='direct',
-                queue_expiration=None,
-                queue_auto_delete=False,
-                durable=self._pika_engine.notification_persistence,
-            )
-            queues_to_consume.add(queue)
-
-        for queue_to_consume in queues_to_consume:
-            self._start_consuming(queue_to_consume)
-
-    def poll(self, timeout=None):
-        msg = super(NotificationPikaListener, self).poll(timeout)
-        if msg is None:
-            return None
-        return pika_message.PikaIncomingMessage(
-            self._pika_engine, *msg, no_ack=self._no_ack
-        )
+                self._reply_queue = None
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index 09559b8a0..f771b7cab 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -22,10 +22,13 @@ from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
 
 from oslo_serialization import jsonutils
 
+from pika import exceptions as pika_exceptions
 from pika import spec as pika_spec
+import pika_pool
 
 import retrying
 import six
+import socket
 import time
 import uuid
 
@@ -47,7 +50,7 @@ class PikaIncomingMessage(object):
 
         self.expiration_time = (
             None if properties.expiration is None else
-            time.time() + int(properties.expiration) / 1000
+            time.time() + float(properties.expiration) / 1000
         )
 
         if self.content_type != "application/json":
@@ -58,10 +61,6 @@ class PikaIncomingMessage(object):
             jsonutils.loads(body, encoding=self.content_encoding)
         )
 
-        self.unique_id = message_dict.pop('_unique_id')
-        self.msg_id = message_dict.pop('_msg_id', None)
-        self.reply_q = message_dict.pop('_reply_q', None)
-
         context_dict = {}
 
         for key in list(message_dict.keys()):
@@ -69,10 +68,31 @@ class PikaIncomingMessage(object):
             if key.startswith('_context_'):
                 value = message_dict.pop(key)
                 context_dict[key[9:]] = value
-
+            elif key.startswith('_'):
+                value = message_dict.pop(key)
+                setattr(self, key[1:], value)
         self.message = message_dict
         self.ctxt = context_dict
 
+    def acknowledge(self):
+        if not self._no_ack:
+            self._channel.basic_ack(delivery_tag=self.delivery_tag)
+
+    def requeue(self):
+        if not self._no_ack:
+            return self._channel.basic_nack(delivery_tag=self.delivery_tag,
+                                            requeue=True)
+
+
+class RpcPikaIncomingMessage(PikaIncomingMessage):
+    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        self.msg_id = None
+        self.reply_q = None
+
+        super(RpcPikaIncomingMessage, self).__init__(
+            pika_engine, channel, method, properties, body, no_ack
+        )
+
     def reply(self, reply=None, failure=None, log_failure=True):
         if not (self.msg_id and self.reply_q):
             return
@@ -83,11 +103,14 @@ class PikaIncomingMessage(object):
         msg = {
             'result': reply,
             'failure': failure,
-            '_unique_id': uuid.uuid4().hex,
             '_msg_id': self.msg_id,
-            'ending': True
         }
 
+        reply_outgoing_message = PikaOutgoingMessage(
+            self._pika_engine, msg, self.ctxt, content_type=self.content_type,
+            content_encoding=self.content_encoding
+        )
+
         def on_exception(ex):
             if isinstance(ex, pika_drv_exc.ConnectionException):
                 LOG.warn(str(ex))
@@ -105,19 +128,12 @@ class PikaIncomingMessage(object):
         )
 
         try:
-            self._pika_engine.publish(
+            reply_outgoing_message.send(
                 exchange=self._pika_engine.rpc_reply_exchange,
                 routing_key=self.reply_q,
-                body=jsonutils.dumps(
-                    common.serialize_msg(msg),
-                    encoding=self.content_encoding
-                ),
-                properties=pika_spec.BasicProperties(
-                    content_encoding=self.content_encoding,
-                    content_type=self.content_type,
-                ),
                 confirm=True,
                 mandatory=False,
+                persistent=False,
                 expiration_time=self.expiration_time,
                 retrier=retrier
             )
@@ -133,18 +149,8 @@ class PikaIncomingMessage(object):
                 )
             )
 
-    def acknowledge(self):
-        if not self._no_ack:
-            self._channel.basic_ack(delivery_tag=self.delivery_tag)
-
-    def requeue(self):
-        if not self._no_ack:
-            return self._channel.basic_nack(delivery_tag=self.delivery_tag,
-                                            requeue=True)
-
 
 class PikaOutgoingMessage(object):
-
     def __init__(self, pika_engine, message, context,
                  content_type="application/json", content_encoding="utf-8"):
         self._pika_engine = pika_engine
@@ -160,11 +166,8 @@ class PikaOutgoingMessage(object):
         self.context = context
 
         self.unique_id = uuid.uuid4().hex
-        self.msg_id = None
 
-    def send(self, exchange, routing_key='', confirm=True,
-             wait_for_reply=False, mandatory=True, persistent=False,
-             timeout=None, retrier=None):
+    def _prepare_message_to_send(self):
         msg = self.message.copy()
 
         msg['_unique_id'] = self.unique_id
@@ -172,46 +175,158 @@ class PikaOutgoingMessage(object):
         for key, value in self.context.iteritems():
             key = six.text_type(key)
             msg['_context_' + key] = value
+        return msg
 
+    @staticmethod
+    def _publish(pool, exchange, routing_key, body, properties, mandatory,
+                 expiration_time):
+        timeout = (None if expiration_time is None else
+                   expiration_time - time.time())
+        if timeout is not None and timeout < 0:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired."
+            )
+        try:
+            with pool.acquire(timeout=timeout) as conn:
+                if timeout is not None:
+                    properties.expiration = str(int(timeout * 1000))
+                conn.channel.publish(
+                    exchange=exchange,
+                    routing_key=routing_key,
+                    body=body,
+                    properties=properties,
+                    mandatory=mandatory
+                )
+        except pika_exceptions.NackError as e:
+            raise pika_drv_exc.MessageRejectedException(
+                "Can not send message: [body: {}], properties: {}] to "
+                "target [exchange: {}, routing_key: {}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except pika_exceptions.UnroutableError as e:
+            raise pika_drv_exc.RoutingException(
+                "Can not deliver message:[body:{}, properties: {}] to any"
+                "queue using target: [exchange:{}, "
+                "routing_key:{}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except pika_pool.Timeout as e:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired. {}".format(str(e))
+            )
+        except pika_pool.Connection.connectivity_errors as e:
+            if (isinstance(e, pika_exceptions.ChannelClosed)
+                    and e.args and e.args[0] == 404):
+                raise pika_drv_exc.ExchangeNotFoundException(
+                    "Attempt to send message to not existing exchange "
+                    "detected, message: [body:{}, properties: {}], target: "
+                    "[exchange:{}, routing_key:{}]. {}".format(
+                        body, properties, exchange, routing_key, str(e)
+                    )
+                )
+
+            raise pika_drv_exc.ConnectionException(
+                "Connectivity problem detected during sending the message: "
+                "[body:{}, properties: {}] to target: [exchange:{}, "
+                "routing_key:{}]. {}".format(
+                    body, properties, exchange, routing_key, str(e)
+                )
+            )
+        except socket.timeout:
+            raise pika_drv_exc.TimeoutConnectionException(
+                "Socket timeout exceeded."
+            )
+
+    def _do_send(self, exchange, routing_key, msg_dict, confirm=True,
+                 mandatory=True, persistent=False, expiration_time=None,
+                 retrier=None):
         properties = pika_spec.BasicProperties(
             content_encoding=self.content_encoding,
             content_type=self.content_type,
             delivery_mode=2 if persistent else 1
         )
 
-        expiration_time = (
-            None if timeout is None else (timeout + time.time())
+        pool = (self._pika_engine.connection_with_confirmation_pool
+                if confirm else self._pika_engine.connection_pool)
+
+        body = jsonutils.dumps(
+            common.serialize_msg(msg_dict),
+            encoding=self.content_encoding
         )
 
-        if wait_for_reply:
-            self.msg_id = uuid.uuid4().hex
-            msg['_msg_id'] = self.msg_id
-            LOG.debug('MSG_ID is %s', self.msg_id)
+        LOG.debug(
+            "Sending message:[body:{}; properties: {}] to target: "
+            "[exchange:{}; routing_key:{}]".format(
+                body, properties, exchange, routing_key
+            )
+        )
 
-            msg['_reply_q'] = self._pika_engine.get_reply_q(timeout)
+        publish = (self._publish if retrier is None else
+                   retrier(self._publish))
 
+        return publish(pool, exchange, routing_key, body, properties,
+                       mandatory, expiration_time)
+
+    def send(self, exchange, routing_key='', confirm=True, mandatory=True,
+             persistent=False, expiration_time=None, retrier=None):
+        msg_dict = self._prepare_message_to_send()
+
+        return self._do_send(exchange, routing_key, msg_dict, confirm,
+                             mandatory, persistent, expiration_time, retrier)
+
+
+class RpcPikaOutgoingMessage(PikaOutgoingMessage):
+    def __init__(self, pika_engine, message, context,
+                 content_type="application/json", content_encoding="utf-8"):
+        super(RpcPikaOutgoingMessage, self).__init__(
+            pika_engine, message, context, content_type, content_encoding
+        )
+        self.msg_id = None
+        self.reply_q = None
+
+    def send(self, target, reply_listener=None, expiration_time=None,
+             retrier=None):
+
+        exchange = self._pika_engine.get_rpc_exchange_name(
+            target.exchange, target.topic, target.fanout, retrier is None
+        )
+
+        queue = "" if target.fanout else self._pika_engine.get_rpc_queue_name(
+            target.topic, target.server, retrier is None
+        )
+
+        msg_dict = self._prepare_message_to_send()
+
+        if reply_listener:
+            msg_id = uuid.uuid4().hex
+            msg_dict["_msg_id"] = msg_id
+            LOG.debug('MSG_ID is %s', msg_id)
+
+            msg_dict["_reply_q"] = reply_listener.get_reply_qname(
+                expiration_time - time.time()
+            )
             future = futures.Future()
 
-            self._pika_engine.register_reply_waiter(
-                msg_id=self.msg_id, future=future,
+            reply_listener.register_reply_waiter(
+                msg_id=msg_id, future=future,
                 expiration_time=expiration_time
             )
 
-        self._pika_engine.publish(
-            exchange=exchange, routing_key=routing_key,
-            body=jsonutils.dumps(
-                common.serialize_msg(msg),
-                encoding=self.content_encoding
-            ),
-            properties=properties,
-            confirm=confirm,
-            mandatory=mandatory,
-            expiration_time=expiration_time,
-            retrier=retrier
-        )
+            self._do_send(
+                exchange=exchange, routing_key=queue, msg_dict=msg_dict,
+                confirm=True, mandatory=True, persistent=False,
+                expiration_time=expiration_time, retrier=retrier
+            )
 
-        if wait_for_reply:
             try:
-                return future.result(timeout)
+                return future.result(expiration_time - time.time())
             except futures.TimeoutError:
                 raise exceptions.MessagingTimeout()
+        else:
+            self._do_send(
+                exchange=exchange, routing_key=queue, msg_dict=msg_dict,
+                confirm=True, mandatory=True, persistent=False,
+                expiration_time=expiration_time, retrier=retrier
+            )
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
new file mode 100644
index 000000000..c7c152609
--- /dev/null
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -0,0 +1,293 @@
+#    Copyright 2015 Mirantis, 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 collections
+
+from oslo_log import log as logging
+
+import threading
+import time
+
+from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
+import pika_pool
+import retrying
+
+LOG = logging.getLogger(__name__)
+
+
+class PikaPoller(object):
+    def __init__(self, pika_engine, prefetch_count):
+        self._pika_engine = pika_engine
+
+        self._connection = None
+        self._channel = None
+        self._lock = threading.Lock()
+
+        self._prefetch_count = prefetch_count
+
+        self._started = False
+
+        self._queues_to_consume = None
+
+        self._message_queue = collections.deque()
+
+    def _reconnect(self):
+        self._connection = self._pika_engine.create_connection(
+            for_listening=True
+        )
+        self._channel = self._connection.channel()
+        self._channel.basic_qos(prefetch_count=self._prefetch_count)
+
+        if self._queues_to_consume is None:
+            self._declare_queue_binding()
+
+        for queue, no_ack in self._queues_to_consume.iteritems():
+            self._start_consuming(queue, no_ack)
+
+    def _declare_queue_binding(self):
+        raise NotImplementedError(
+            "It is base class. Please declare exchanges and queues here"
+        )
+
+    def _start_consuming(self, queue, no_ack):
+        on_message_no_ack_callback = (
+            self._on_message_no_ack_callback if no_ack
+            else self._on_message_with_ack_callback
+        )
+
+        try:
+            self._channel.basic_consume(on_message_no_ack_callback, queue,
+                                        no_ack=no_ack)
+        except Exception:
+            self._queues_to_consume = None
+            raise
+
+    def _on_message_no_ack_callback(self, unused, method, properties, body):
+        self._message_queue.append(
+            (self._channel, method, properties, body, True)
+        )
+
+    def _on_message_with_ack_callback(self, unused, method, properties, body):
+        self._message_queue.append(
+            (self._channel, method, properties, body, False)
+        )
+
+    def _cleanup(self):
+        if self._channel:
+            try:
+                self._channel.close()
+            except Exception as ex:
+                if not pika_pool.Connection.is_connection_invalidated(ex):
+                    LOG.exception("Unexpected error during closing channel")
+            self._channel = None
+
+        if self._connection:
+            try:
+                self._connection.close()
+            except Exception as ex:
+                if not pika_pool.Connection.is_connection_invalidated(ex):
+                    LOG.exception("Unexpected error during closing connection")
+            self._connection = None
+
+    def poll(self, timeout=None):
+        start = time.time()
+        while not self._message_queue:
+            with self._lock:
+                if not self._started:
+                    return None
+
+                try:
+                    if self._channel is None:
+                        self._reconnect()
+                    self._connection.process_data_events()
+                except Exception:
+                    self._cleanup()
+                    raise
+            if timeout and time.time() - start > timeout:
+                return None
+
+        return self._message_queue.popleft()
+
+    def start(self):
+        self._started = True
+
+    def stop(self):
+        with self._lock:
+            if not self._started:
+                return
+
+            self._started = False
+            self._cleanup()
+
+    def reconnect(self):
+        with self._lock:
+            self._cleanup()
+            try:
+                self._reconnect()
+            except Exception:
+                self._cleanup()
+                raise
+
+    def cleanup(self):
+        with self._lock:
+            self._cleanup()
+
+
+class RpcServicePikaPoller(PikaPoller):
+    def __init__(self, pika_engine, target, prefetch_count):
+        self._target = target
+
+        super(RpcServicePikaPoller, self).__init__(
+            pika_engine, prefetch_count=prefetch_count)
+
+    def _declare_queue_binding(self):
+        queue_expiration = (
+            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
+        queues_to_consume = {}
+
+        for no_ack in [True, False]:
+            exchange = self._pika_engine.get_rpc_exchange_name(
+                self._target.exchange, self._target.topic, False, no_ack
+            )
+            fanout_exchange = self._pika_engine.get_rpc_exchange_name(
+                self._target.exchange, self._target.topic, True, no_ack
+            )
+            queue = self._pika_engine.get_rpc_queue_name(
+                self._target.topic, None, no_ack
+            )
+            server_queue = self._pika_engine.get_rpc_queue_name(
+                self._target.topic, self._target.server, no_ack
+            )
+
+            queues_to_consume[queue] = no_ack
+            queues_to_consume[server_queue] = no_ack
+
+            self._pika_engine.declare_queue_binding_by_channel(
+                channel=self._channel, exchange=exchange, queue=queue,
+                routing_key=queue, exchange_type='direct', durable=False,
+                queue_expiration=queue_expiration, queue_auto_delete=False
+            )
+            self._pika_engine.declare_queue_binding_by_channel(
+                channel=self._channel, exchange=exchange, queue=server_queue,
+                routing_key=server_queue, exchange_type='direct',
+                queue_expiration=queue_expiration, queue_auto_delete=False,
+                durable=False
+            )
+            self._pika_engine.declare_queue_binding_by_channel(
+                channel=self._channel, exchange=fanout_exchange, durable=False,
+                queue=server_queue, routing_key="", exchange_type='fanout',
+                queue_expiration=queue_expiration, queue_auto_delete=False,
+            )
+        self._queues_to_consume = queues_to_consume
+
+    def poll(self, timeout=None):
+        msg = super(RpcServicePikaPoller, self).poll(timeout)
+        if msg is None:
+            return None
+        return pika_drv_msg.RpcPikaIncomingMessage(
+            self._pika_engine, *msg
+        )
+
+
+class RpcReplyPikaPoller(PikaPoller):
+    def __init__(self, pika_engine, exchange, queue, prefetch_count):
+        self._exchange = exchange
+        self._queue = queue
+
+        super(RpcReplyPikaPoller, self).__init__(
+            pika_engine, prefetch_count
+        )
+
+    def _declare_queue_binding(self):
+        queue_expiration = (
+            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
+        self._pika_engine.declare_queue_binding_by_channel(
+            channel=self._channel,
+            exchange=self._exchange, queue=self._queue,
+            routing_key=self._queue, exchange_type='direct',
+            queue_expiration=queue_expiration, queue_auto_delete=False,
+            durable=False
+        )
+
+        self._queues_to_consume = {self._queue: False}
+
+    def start(self, timeout=None):
+        super(RpcReplyPikaPoller, self).start()
+
+        def on_exception(ex):
+            LOG.warn(str(ex))
+
+            return True
+
+        retrier = retrying.retry(
+            stop_max_attempt_number=self._pika_engine.rpc_reply_retry_attempts,
+            stop_max_delay=None if timeout is None else timeout * 1000,
+            wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
+            retry_on_exception=on_exception,
+        )
+
+        retrier(self.reconnect)()
+
+    def poll(self, timeout=None):
+        msg = super(RpcReplyPikaPoller, self).poll(timeout)
+        if msg is None:
+            return None
+        return pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, *msg
+        )
+
+
+class NotificationPikaPoller(PikaPoller):
+    def __init__(self, pika_engine, targets_and_priorities,
+                 queue_name=None, prefetch_count=100):
+        self._targets_and_priorities = targets_and_priorities
+        self._queue_name = queue_name
+
+        super(NotificationPikaPoller, self).__init__(
+            pika_engine, prefetch_count=prefetch_count
+        )
+
+    def _declare_queue_binding(self):
+        queues_to_consume = {}
+        for target, priority in self._targets_and_priorities:
+            routing_key = '%s.%s' % (target.topic, priority)
+            queue = self._queue_name or routing_key
+            self._pika_engine.declare_queue_binding_by_channel(
+                channel=self._channel,
+                exchange=(
+                    target.exchange or
+                    self._pika_engine.default_notification_exchange
+                ),
+                queue = queue,
+                routing_key=routing_key,
+                exchange_type='direct',
+                queue_expiration=None,
+                queue_auto_delete=False,
+                durable=self._pika_engine.notification_persistence,
+            )
+            queues_to_consume[queue] = False
+
+        self._queues_to_consume = queues_to_consume
+
+    def poll(self, timeout=None):
+        msg = super(NotificationPikaPoller, self).poll(timeout)
+        if msg is None:
+            return None
+        return pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, *msg
+        )

From e24f4faf9681525817f33966c3d5602075febc7c Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Thu, 19 Nov 2015 14:51:02 +0200
Subject: [PATCH 07/16] Fix delay before host reconnecting

Change-Id: Ifd66b3fe44f5451ef4b5e03d06f214199474ca0b
---
 oslo_messaging/_drivers/impl_pika.py          |  2 +-
 .../_drivers/pika_driver/pika_engine.py       |  4 +-
 .../_drivers/pika_driver/pika_listener.py     | 37 ++++++++-----------
 .../_drivers/pika_driver/pika_message.py      |  5 ++-
 .../_drivers/pika_driver/pika_poller.py       | 13 +++++--
 5 files changed, 31 insertions(+), 30 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index b4b6d4288..843753648 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -47,7 +47,7 @@ pika_opts = [
     cfg.FloatOpt('tcp_user_timeout', default=0.25,
                  help="Set TCP_USER_TIMEOUT in seconds for connection's "
                       "socket"),
-    cfg.FloatOpt('host_connection_reconnect_delay', default=5,
+    cfg.FloatOpt('host_connection_reconnect_delay', default=0.25,
                  help="Set delay for reconnection to some host which has "
                       "connection error")
 ]
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 03ccccdf0..9d203bfce 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -121,7 +121,7 @@ class PikaEngine(object):
                              "integer")
 
         self._tcp_user_timeout = self.conf.oslo_messaging_pika.tcp_user_timeout
-        self._host_connection_reconnect_delay = (
+        self.host_connection_reconnect_delay = (
             self.conf.oslo_messaging_pika.host_connection_reconnect_delay
         )
 
@@ -249,7 +249,7 @@ class PikaEngine(object):
             ]
             if (last_time != last_success_time and
                     cur_time - last_time <
-                    self._host_connection_reconnect_delay):
+                    self.host_connection_reconnect_delay):
                 raise pika_drv_exc.HostConnectionNotAllowedException(
                     "Connection to host #{} is not allowed now because of "
                     "previous failure".format(host_index)
diff --git a/oslo_messaging/_drivers/pika_driver/pika_listener.py b/oslo_messaging/_drivers/pika_driver/pika_listener.py
index 493adf1ea..88fc2586c 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_listener.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_listener.py
@@ -14,13 +14,14 @@
 
 from oslo_log import log as logging
 
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
 from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
 
-
 import threading
 import time
 import uuid
 
+
 LOG = logging.getLogger(__name__)
 
 
@@ -32,7 +33,7 @@ class RpcReplyPikaListener(object):
         self._reply_queue = None
 
         self._reply_poller = None
-        self._reply_waiting_future_list = []
+        self._reply_waiting_futures = {}
 
         self._reply_consumer_enabled = False
         self._reply_consumer_thread_run_flag = True
@@ -83,27 +84,21 @@ class RpcReplyPikaListener(object):
                 if message is None:
                     continue
                 message.acknowledge()
-                i = 0
-                curtime = time.time()
-                while (i < len(self._reply_waiting_future_list) and
-                        self._reply_consumer_thread_run_flag):
-                    msg_id, future, expiration = (
-                        self._reply_waiting_future_list[i]
-                    )
-                    if expiration and expiration < curtime:
-                        del self._reply_waiting_future_list[i]
-                    elif msg_id == message.msg_id:
-                        del self._reply_waiting_future_list[i]
-                        future.set_result(message)
-                    else:
-                        i += 1
+                future = self._reply_waiting_futures.pop(message.msg_id, None)
+                if future is not None:
+                    future.set_result(message)
+            except pika_drv_exc.EstablishConnectionException:
+                LOG.exception("Problem during establishing connection for "
+                              "reply polling")
+                time.sleep(self._pika_engine.host_connection_reconnect_delay)
             except BaseException:
-                LOG.exception("Exception during reply polling")
+                LOG.exception("Unexpected exception during reply polling")
 
-    def register_reply_waiter(self, msg_id, future, expiration_time):
-        self._reply_waiting_future_list.append(
-            (msg_id, future, expiration_time)
-        )
+    def register_reply_waiter(self, msg_id, future):
+        self._reply_waiting_futures[msg_id] = future
+
+    def unregister_reply_waiter(self, msg_id):
+        self._reply_waiting_futures.pop(msg_id, None)
 
     def cleanup(self):
         with self._reply_consumer_lock:
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index f771b7cab..da87c7d01 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -310,8 +310,7 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
             future = futures.Future()
 
             reply_listener.register_reply_waiter(
-                msg_id=msg_id, future=future,
-                expiration_time=expiration_time
+                msg_id=msg_id, future=future
             )
 
             self._do_send(
@@ -324,6 +323,8 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
                 return future.result(expiration_time - time.time())
             except futures.TimeoutError:
                 raise exceptions.MessagingTimeout()
+            finally:
+                reply_listener.unregister_reply_waiter(self.msg_id)
         else:
             self._do_send(
                 exchange=exchange, routing_key=queue, msg_dict=msg_dict,
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index c7c152609..d34a9c11a 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -101,7 +101,8 @@ class PikaPoller(object):
             self._connection = None
 
     def poll(self, timeout=None):
-        start = time.time()
+        expiration_time = time.time() + timeout if timeout else None
+
         while not self._message_queue:
             with self._lock:
                 if not self._started:
@@ -110,12 +111,16 @@ class PikaPoller(object):
                 try:
                     if self._channel is None:
                         self._reconnect()
-                    self._connection.process_data_events()
+                    self._connection.process_data_events(
+                        time_limit=timeout
+                    )
                 except Exception:
                     self._cleanup()
                     raise
-            if timeout and time.time() - start > timeout:
-                return None
+            if timeout is not None:
+                timeout = expiration_time - time.time()
+                if timeout <= 0:
+                    return None
 
         return self._message_queue.popleft()
 

From 8caa4bef8412a03bae45aaab675b4c98e2ef7fe3 Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Mon, 23 Nov 2015 14:19:44 +0200
Subject: [PATCH 08/16] Removes additional select module patching

Change-Id: I9ad8a3ffec2c41ef7b88a522e0d643d29be86af0
---
 oslo_messaging/_drivers/impl_pika.py          | 29 ++-----------
 .../_drivers/pika_driver/pika_engine.py       | 41 +++++++++++++++----
 2 files changed, 36 insertions(+), 34 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index 843753648..bdab583e2 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -12,10 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import retrying
-import sys
-import time
-
 from oslo_config import cfg
 from oslo_log import log as logging
 
@@ -29,6 +25,9 @@ from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
 
 from oslo_messaging import exceptions
 
+import retrying
+import time
+
 LOG = logging.getLogger(__name__)
 
 pika_opts = [
@@ -133,31 +132,9 @@ rpc_opts = [
 ]
 
 
-def _is_eventlet_monkey_patched():
-    if 'eventlet.patcher' not in sys.modules:
-        return False
-    import eventlet.patcher
-    return eventlet.patcher.is_monkey_patched('thread')
-
-
 class PikaDriver(object):
     def __init__(self, conf, url, default_exchange=None,
                  allowed_remote_exmods=None):
-        if 'eventlet.patcher' in sys.modules:
-            import eventlet.patcher
-            if eventlet.patcher.is_monkey_patched('select'):
-                import select
-
-                try:
-                    del select.poll
-                except AttributeError:
-                    pass
-
-                try:
-                    del select.epoll
-                except AttributeError:
-                    pass
-
         opt_group = cfg.OptGroup(name='oslo_messaging_pika',
                                  title='Pika driver options')
         conf.register_group(opt_group)
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 9d203bfce..745c2da1c 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -11,8 +11,6 @@
 #    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 socket
-
 from oslo_log import log as logging
 
 from oslo_messaging import exceptions
@@ -20,18 +18,41 @@ from oslo_messaging import exceptions
 from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
 
 import pika
-from pika import adapters as pika_adapters
+from pika.adapters import select_connection
 from pika import credentials as pika_credentials
 
 import pika_pool
 
+import socket
+import sys
+
 import threading
 import time
 
 LOG = logging.getLogger(__name__)
 
 
-class PooledConnectionWithConfirmations(pika_pool.Connection):
+def _is_eventlet_monkey_patched(module):
+    if 'eventlet.patcher' not in sys.modules:
+        return False
+    import eventlet.patcher
+    return eventlet.patcher.is_monkey_patched(module)
+
+
+def _create__select_poller_connection_impl(
+        parameters, on_open_callback, on_open_error_callback,
+        on_close_callback, stop_ioloop_on_close):
+    return select_connection.SelectConnection(
+        parameters=parameters,
+        on_open_callback=on_open_callback,
+        on_open_error_callback=on_open_error_callback,
+        on_close_callback=on_close_callback,
+        stop_ioloop_on_close=stop_ioloop_on_close,
+        custom_ioloop=select_connection.SelectPoller()
+    )
+
+
+class _PooledConnectionWithConfirmations(pika_pool.Connection):
     @property
     def channel(self):
         if self.fairy.channel is None:
@@ -49,6 +70,8 @@ class PikaEngine(object):
     def __init__(self, conf, url, default_exchange=None):
         self.conf = conf
 
+        self._force_select_poller_use = _is_eventlet_monkey_patched('select')
+
         # processing rpc options
 
         self.default_rpc_exchange = (
@@ -177,7 +200,7 @@ class PikaEngine(object):
         )
 
         self.connection_with_confirmation_pool.Connection = (
-            PooledConnectionWithConfirmations
+            _PooledConnectionWithConfirmations
         )
 
     def _next_connection_num(self):
@@ -258,14 +281,16 @@ class PikaEngine(object):
             try:
                 base_host_params = self._connection_host_param_list[host_index]
 
-                connection = pika_adapters.BlockingConnection(
-                    pika.ConnectionParameters(
+                connection = pika.BlockingConnection(
+                    parameters=pika.ConnectionParameters(
                         heartbeat_interval=(
                             self.conf.oslo_messaging_pika.heartbeat_interval
                             if for_listening else None
                         ),
                         **base_host_params
-                    )
+                    ),
+                    _impl_class=(_create__select_poller_connection_impl
+                                 if self._force_select_poller_use else None)
                 )
 
                 self._set_tcp_user_timeout(connection._impl.socket)

From 46ac91e63ed39ec575b1b4a9f47becc87eb80511 Mon Sep 17 00:00:00 2001
From: Alexey Lebedeff <alebedev@mirantis.com>
Date: Tue, 24 Nov 2015 14:57:24 +0300
Subject: [PATCH 09/16] Provide missing parts of error messages

Change-Id: I8ccb8a4979ec78b7f87d32d455265eeb57ed442f
---
 .../_drivers/pika_driver/pika_message.py         | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index da87c7d01..b08288c71 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -54,8 +54,12 @@ class PikaIncomingMessage(object):
         )
 
         if self.content_type != "application/json":
-            raise NotImplementedError("Content-type['{}'] is not valid, "
-                                      "'application/json' only is supported.")
+            raise NotImplementedError(
+                "Content-type['{}'] is not valid, "
+                "'application/json' only is supported.".format(
+                    self.content_type
+                )
+            )
 
         message_dict = common.deserialize_msg(
             jsonutils.loads(body, encoding=self.content_encoding)
@@ -159,8 +163,12 @@ class PikaOutgoingMessage(object):
         self.content_encoding = content_encoding
 
         if self.content_type != "application/json":
-            raise NotImplementedError("Content-type['{}'] is not valid, "
-                                      "'application/json' only is supported.")
+            raise NotImplementedError(
+                "Content-type['{}'] is not valid, "
+                "'application/json' only is supported.".format(
+                    self.content_type
+                )
+            )
 
         self.message = message
         self.context = context

From a30fcdf585653fb0b18d39e55695be5efd82a5c8 Mon Sep 17 00:00:00 2001
From: dukhlov <dukhlov@mirantis.com>
Date: Fri, 27 Nov 2015 14:42:56 -0500
Subject: [PATCH 10/16] Adds comments and small fixes

Fixes in this patch:
1) Set time_timit=0.25 for process_data_events in poll
   It is needed to release lock time to time and give
   a chance another method to be executed
2) move declare_queue_bindind method from pika_engine
   to impl_pika module because it is not common and used
   only there
3) small optimization - now unregister_reply_waiter
   is called in worst case only - if we have not got
   reply by timeout expiration

Change-Id: I18b6cdec9c1f746086a5a1ae4ecd8d8ecdaa9468
---
 oslo_messaging/_drivers/impl_pika.py          |  40 ++++--
 .../_drivers/pika_driver/pika_engine.py       | 130 ++++++++++++------
 .../_drivers/pika_driver/pika_exceptions.py   |  34 +++--
 .../_drivers/pika_driver/pika_listener.py     |  80 +++++++----
 .../_drivers/pika_driver/pika_message.py      |  10 +-
 .../_drivers/pika_driver/pika_poller.py       |  16 ++-
 6 files changed, 211 insertions(+), 99 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index bdab583e2..4ccf346d4 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -25,6 +25,8 @@ from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
 
 from oslo_messaging import exceptions
 
+import pika_pool
+
 import retrying
 import time
 
@@ -198,6 +200,31 @@ class PikaDriver(object):
 
             return reply.message['result']
 
+    def _declare_notification_queue_binding(self, target, timeout=None):
+        if timeout is not None and timeout < 0:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired."
+            )
+        try:
+            with self._pika_engine.connection_pool.acquire(
+                    timeout=timeout) as conn:
+                self._pika_engine.declare_queue_binding_by_channel(
+                    conn.channel,
+                    exchange=(
+                        target.exchange or
+                        self._pika_engine.default_notification_exchange
+                    ),
+                    queue=target.topic,
+                    routing_key=target.topic,
+                    exchange_type='direct',
+                    queue_expiration=None,
+                    durable=self._pika_engine.notification_persistence,
+                )
+        except pika_pool.Timeout as e:
+            raise exceptions.MessagingTimeout(
+                "Timeout for current operation was expired. {}.".format(str(e))
+            )
+
     def send_notification(self, target, ctxt, message, version, retry=None):
         if retry is None:
             retry = self._pika_engine.default_notification_retry_attempts
@@ -207,18 +234,7 @@ class PikaDriver(object):
                                pika_drv_exc.RoutingException)):
                 LOG.warn(str(ex))
                 try:
-                    self._pika_engine.declare_queue_binding(
-                        exchange=(
-                            target.exchange or
-                            self._pika_engine.default_notification_exchange
-                        ),
-                        queue=target.topic,
-                        routing_key=target.topic,
-                        exchange_type='direct',
-                        queue_expiration=None,
-                        queue_auto_delete=False,
-                        durable=self._pika_engine.notification_persistence,
-                    )
+                    self._declare_notification_queue_binding(target)
                 except pika_drv_exc.ConnectionException as e:
                     LOG.warn(str(e))
                 return True
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 745c2da1c..f8060cc13 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -13,8 +13,6 @@
 #    under the License.
 from oslo_log import log as logging
 
-from oslo_messaging import exceptions
-
 from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
 
 import pika
@@ -33,6 +31,12 @@ LOG = logging.getLogger(__name__)
 
 
 def _is_eventlet_monkey_patched(module):
+    """Determines safely is eventlet patching for module enabled or not
+
+    :param module: String, module name
+    :return Bool, True if module is pathed, False otherwise
+    """
+
     if 'eventlet.patcher' not in sys.modules:
         return False
     import eventlet.patcher
@@ -42,6 +46,14 @@ def _is_eventlet_monkey_patched(module):
 def _create__select_poller_connection_impl(
         parameters, on_open_callback, on_open_error_callback,
         on_close_callback, stop_ioloop_on_close):
+    """Used for disabling autochoise of poller ('select', 'poll', 'epool', etc)
+    inside default 'SelectConnection.__init__(...)' logic. It is necessary to
+    force 'select' poller usage if eventlet is monkeypatched because eventlet
+    patches only 'select' system call
+
+    Method signature is copied form 'SelectConnection.__init__(...)', because
+    it is used as replacement of 'SelectConnection' class to create instances
+    """
     return select_connection.SelectConnection(
         parameters=parameters,
         on_open_callback=on_open_callback,
@@ -53,6 +65,10 @@ def _create__select_poller_connection_impl(
 
 
 class _PooledConnectionWithConfirmations(pika_pool.Connection):
+    """Derived from 'pika_pool.Connection' and extends its logic - adds
+    'confirm_delivery' call after channel creation to enable delivery
+    confirmation for channel
+    """
     @property
     def channel(self):
         if self.fairy.channel is None:
@@ -62,9 +78,17 @@ class _PooledConnectionWithConfirmations(pika_pool.Connection):
 
 
 class PikaEngine(object):
+    """Used for shared functionality between other pika driver modules, like
+    connection factory, connection pools, processing and holding configuration,
+    etc.
+    """
+
+    # constants for creating connection statistics
     HOST_CONNECTION_LAST_TRY_TIME = "last_try_time"
     HOST_CONNECTION_LAST_SUCCESS_TRY_TIME = "last_success_try_time"
 
+    # constant for setting tcp_user_timeout socket option
+    # (it should be defined in 'select' module of standard library in future)
     TCP_USER_TIMEOUT = 18
 
     def __init__(self, conf, url, default_exchange=None):
@@ -73,7 +97,6 @@ class PikaEngine(object):
         self._force_select_poller_use = _is_eventlet_monkey_patched('select')
 
         # processing rpc options
-
         self.default_rpc_exchange = (
             conf.oslo_messaging_pika.default_rpc_exchange if
             conf.oslo_messaging_pika.default_rpc_exchange else
@@ -106,6 +129,10 @@ class PikaEngine(object):
             raise ValueError("rpc_reply_retry_delay should be non-negative "
                              "integer")
 
+        self.rpc_queue_expiration = (
+            self.conf.oslo_messaging_pika.rpc_queue_expiration
+        )
+
         # processing notification options
         self.default_notification_exchange = (
             conf.oslo_messaging_pika.default_notification_exchange if
@@ -147,6 +174,9 @@ class PikaEngine(object):
         self.host_connection_reconnect_delay = (
             self.conf.oslo_messaging_pika.host_connection_reconnect_delay
         )
+        self._heartbeat_interval = (
+            self.conf.oslo_messaging_pika.heartbeat_interval
+        )
 
         # initializing connection parameters for configured RabbitMQ hosts
         common_pika_params = {
@@ -204,6 +234,11 @@ class PikaEngine(object):
         )
 
     def _next_connection_num(self):
+        """Used for creating connections to different RabbitMQ nodes in
+        round robin order
+
+        :return: next host number to create connection to
+        """
         with self._connection_lock:
             cur_num = self._next_connection_host_num
             self._next_connection_host_num += 1
@@ -215,7 +250,7 @@ class PikaEngine(object):
     def create_connection(self, for_listening=False):
         """Create and return connection to any available host.
 
-        :return: cerated connection
+        :return: created connection
         :raise: ConnectionException if all hosts are not reachable
         """
         host_count = len(self._connection_host_param_list)
@@ -257,7 +292,9 @@ class PikaEngine(object):
 
     def create_host_connection(self, host_index, for_listening=False):
         """Create new connection to host #host_index
-
+        :param host_index: Integer, number of host for connection establishing
+        :param for_listening: Boolean, creates connection for listening
+            (enable heartbeats) if True
         :return: New connection
         """
 
@@ -270,6 +307,10 @@ class PikaEngine(object):
             last_time = self._connection_host_status_list[host_index][
                 self.HOST_CONNECTION_LAST_TRY_TIME
             ]
+
+            # raise HostConnectionNotAllowedException if we tried to establish
+            # connection in last 'host_connection_reconnect_delay' and got
+            # failure
             if (last_time != last_success_time and
                     cur_time - last_time <
                     self.host_connection_reconnect_delay):
@@ -284,7 +325,7 @@ class PikaEngine(object):
                 connection = pika.BlockingConnection(
                     parameters=pika.ConnectionParameters(
                         heartbeat_interval=(
-                            self.conf.oslo_messaging_pika.heartbeat_interval
+                            self._heartbeat_interval
                             if for_listening else None
                         ),
                         **base_host_params
@@ -308,52 +349,53 @@ class PikaEngine(object):
     @staticmethod
     def declare_queue_binding_by_channel(channel, exchange, queue, routing_key,
                                          exchange_type, queue_expiration,
-                                         queue_auto_delete, durable):
-        channel.exchange_declare(
-            exchange, exchange_type, auto_delete=True, durable=durable
-        )
-        arguments = {}
+                                         durable):
+        """Declare exchange, queue and bind them using already created
+        channel, if they don't exist
 
-        if queue_expiration > 0:
-            arguments['x-expires'] = queue_expiration * 1000
-
-        channel.queue_declare(
-            queue, auto_delete=queue_auto_delete, durable=durable,
-            arguments=arguments
-        )
-
-        channel.queue_bind(queue, exchange, routing_key)
-
-    def declare_queue_binding(self, exchange, queue, routing_key,
-                              exchange_type, queue_expiration,
-                              queue_auto_delete, durable,
-                              timeout=None):
-        if timeout is not None and timeout < 0:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired."
-            )
+        :param channel: Channel for communication with RabbitMQ
+        :param exchange:  String, RabbitMQ exchange name
+        :param queue: Sting, RabbitMQ queue name
+        :param routing_key: Sting, RabbitMQ routing key for queue binding
+        :param exchange_type: String ('direct', 'topic' or 'fanout')
+            exchange type for exchange to be declared
+        :param queue_expiration: Integer, time in seconds which queue will
+            remain existing in RabbitMQ when there no consumers connected
+        :param durable: Boolean, creates durable exchange and queue if true
+        """
         try:
-            with self.connection_pool.acquire(timeout=timeout) as conn:
-                self.declare_queue_binding_by_channel(
-                    conn.channel, exchange, queue, routing_key, exchange_type,
-                    queue_expiration, queue_auto_delete, durable
-                )
-        except pika_pool.Timeout as e:
-            raise exceptions.MessagingTimeout(
-                "Timeout for current operation was expired. {}.".format(str(e))
+            channel.exchange_declare(
+                exchange, exchange_type, auto_delete=True, durable=durable
             )
+            arguments = {}
+
+            if queue_expiration > 0:
+                arguments['x-expires'] = queue_expiration * 1000
+
+            channel.queue_declare(queue, durable=durable, arguments=arguments)
+
+            channel.queue_bind(queue, exchange, routing_key)
         except pika_pool.Connection.connectivity_errors as e:
             raise pika_drv_exc.ConnectionException(
                 "Connectivity problem detected during declaring queue "
                 "binding: exchange:{}, queue: {}, routing_key: {}, "
-                "exchange_type: {}, queue_expiration: {}, queue_auto_delete: "
-                "{}, durable: {}. {}".format(
+                "exchange_type: {}, queue_expiration: {}, "
+                "durable: {}. {}".format(
                     exchange, queue, routing_key, exchange_type,
-                    queue_expiration, queue_auto_delete, durable, str(e)
+                    queue_expiration, durable, str(e)
                 )
             )
 
     def get_rpc_exchange_name(self, exchange, topic, fanout, no_ack):
+        """Returns RabbitMQ exchange name for given rpc request
+
+        :param exchange: String, oslo.messaging target's exchange
+        :param topic: String, oslo.messaging target's topic
+        :param fanout: Boolean, oslo.messaging target's fanout mode
+        :param no_ack: Boolean, use message delivery with acknowledges or not
+
+        :return: String, RabbitMQ exchange name
+        """
         exchange = (exchange or self.default_rpc_exchange)
 
         if fanout:
@@ -364,6 +406,14 @@ class PikaEngine(object):
 
     @staticmethod
     def get_rpc_queue_name(topic, server, no_ack):
+        """Returns RabbitMQ queue name for given rpc request
+
+        :param topic: String, oslo.messaging target's topic
+        :param server: String, oslo.messaging target's server
+        :param no_ack: Boolean, use message delivery with acknowledges or not
+
+        :return: String, RabbitMQ exchange name
+        """
         queue_parts = ["no_ack" if no_ack else "with_ack", topic]
         if server is not None:
             queue_parts.append(server)
diff --git a/oslo_messaging/_drivers/pika_driver/pika_exceptions.py b/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
index 62e370fa3..89e191560 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
@@ -16,28 +16,46 @@ from oslo_messaging import exceptions
 
 
 class ExchangeNotFoundException(exceptions.MessageDeliveryFailure):
+    """Is raised if specified exchange is not found in RabbitMQ."""
     pass
 
 
 class MessageRejectedException(exceptions.MessageDeliveryFailure):
+    """Is raised if message which you are trying to send was nacked by RabbitMQ
+    it may happen if RabbitMQ is not able to process message
+    """
     pass
 
 
 class RoutingException(exceptions.MessageDeliveryFailure):
+    """Is raised if message can not be delivered to any queue. Usually it means
+    that any queue is not binded to given exchange with given routing key.
+    Raised if 'mandatory' flag specified only
+    """
     pass
 
 
 class ConnectionException(exceptions.MessagingException):
-    pass
-
-
-class HostConnectionNotAllowedException(ConnectionException):
-    pass
-
-
-class EstablishConnectionException(ConnectionException):
+    """Is raised if some operation can not be performed due to connectivity
+    problem
+    """
     pass
 
 
 class TimeoutConnectionException(ConnectionException):
+    """Is raised if socket timeout was expired during network interaction"""
+    pass
+
+
+class EstablishConnectionException(ConnectionException):
+    """Is raised if we have some problem during establishing connection
+    procedure
+    """
+    pass
+
+
+class HostConnectionNotAllowedException(EstablishConnectionException):
+    """Is raised in case of try to establish connection to temporary
+    not allowed host (because of reconnection policy for example)
+    """
     pass
diff --git a/oslo_messaging/_drivers/pika_driver/pika_listener.py b/oslo_messaging/_drivers/pika_driver/pika_listener.py
index 88fc2586c..cfbb5b8de 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_listener.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_listener.py
@@ -26,6 +26,10 @@ LOG = logging.getLogger(__name__)
 
 
 class RpcReplyPikaListener(object):
+    """Provide functionality for listening RPC replies. Create and handle
+    reply poller and coroutine for performing polling job
+    """
+
     def __init__(self, pika_engine):
         self._pika_engine = pika_engine
 
@@ -35,25 +39,34 @@ class RpcReplyPikaListener(object):
         self._reply_poller = None
         self._reply_waiting_futures = {}
 
-        self._reply_consumer_enabled = False
-        self._reply_consumer_thread_run_flag = True
-        self._reply_consumer_lock = threading.Lock()
-        self._puller_thread = None
+        self._reply_consumer_initialized = False
+        self._reply_consumer_initialization_lock = threading.Lock()
+        self._poller_thread = None
 
     def get_reply_qname(self, expiration_time=None):
-        if self._reply_consumer_enabled:
+        """As result return reply queue name, shared for whole process,
+        but before this check is RPC listener initialized or not and perform
+        initialization if needed
+
+        :param expiration_time: Float, expiration time in seconds
+            (like time.time()),
+        :return: String, queue name which hould be used for reply sending
+        """
+        if self._reply_consumer_initialized:
             return self._reply_queue
 
-        with self._reply_consumer_lock:
-            if self._reply_consumer_enabled:
+        with self._reply_consumer_initialization_lock:
+            if self._reply_consumer_initialized:
                 return self._reply_queue
 
+            # generate reply queue name if needed
             if self._reply_queue is None:
                 self._reply_queue = "reply.{}.{}.{}".format(
                     self._pika_engine.conf.project,
                     self._pika_engine.conf.prog, uuid.uuid4().hex
                 )
 
+            # initialize reply poller if needed
             if self._reply_poller is None:
                 self._reply_poller = pika_drv_poller.RpcReplyPikaPoller(
                     pika_engine=self._pika_engine,
@@ -66,21 +79,25 @@ class RpcReplyPikaListener(object):
 
                 self._reply_poller.start(timeout=expiration_time - time.time())
 
-            if self._puller_thread is None:
-                self._puller_thread = threading.Thread(target=self._poller)
-                self._puller_thread.daemon = True
+            # start reply poller job thread if needed
+            if self._poller_thread is None:
+                self._poller_thread = threading.Thread(target=self._poller)
+                self._poller_thread.daemon = True
 
-            if not self._puller_thread.is_alive():
-                self._puller_thread.start()
+            if not self._poller_thread.is_alive():
+                self._poller_thread.start()
 
-            self._reply_consumer_enabled = True
+            self._reply_consumer_initialized = True
 
         return self._reply_queue
 
     def _poller(self):
-        while self._reply_consumer_thread_run_flag:
+        """Reply polling job. Poll replies in infinite loop and notify
+        registered features
+        """
+        while self._reply_poller:
             try:
-                message = self._reply_poller.poll(timeout=1)
+                message = self._reply_poller.poll()
                 if message is None:
                     continue
                 message.acknowledge()
@@ -95,24 +112,31 @@ class RpcReplyPikaListener(object):
                 LOG.exception("Unexpected exception during reply polling")
 
     def register_reply_waiter(self, msg_id, future):
+        """Register reply waiter. Should be called before message sending to
+        the server
+        :param msg_id: String, message_id of expected reply
+        :param future: Future, container for expected reply to be returned over
+        """
         self._reply_waiting_futures[msg_id] = future
 
     def unregister_reply_waiter(self, msg_id):
+        """Unregister reply waiter. Should be called if client has not got
+        reply and doesn't want to continue waiting (if timeout_expired for
+        example)
+        :param msg_id:
+        """
         self._reply_waiting_futures.pop(msg_id, None)
 
     def cleanup(self):
-        with self._reply_consumer_lock:
-            self._reply_consumer_enabled = False
+        """Stop replies consuming and cleanup resources"""
+        if self._reply_poller:
+            self._reply_poller.stop()
+            self._reply_poller.cleanup()
+            self._reply_poller = None
 
-            if self._puller_thread:
-                if self._puller_thread.is_alive():
-                    self._reply_consumer_thread_run_flag = False
-                    self._puller_thread.join()
-                self._puller_thread = None
+        if self._poller_thread:
+            if self._poller_thread.is_alive():
+                self._poller_thread.join()
+            self._poller_thread = None
 
-            if self._reply_poller:
-                self._reply_poller.stop()
-                self._reply_poller.cleanup()
-                self._reply_poller = None
-
-                self._reply_queue = None
+        self._reply_queue = None
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index da87c7d01..2b4bbbc26 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -125,7 +125,7 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
             ),
             retry_on_exception=on_exception,
             wait_fixed=self._pika_engine.rpc_reply_retry_delay * 1000,
-        )
+        ) if self._pika_engine.rpc_reply_retry_attempts else None
 
         try:
             reply_outgoing_message.send(
@@ -321,10 +321,12 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
 
             try:
                 return future.result(expiration_time - time.time())
-            except futures.TimeoutError:
-                raise exceptions.MessagingTimeout()
-            finally:
+            except BaseException as e:
                 reply_listener.unregister_reply_waiter(self.msg_id)
+                if isinstance(e, futures.TimeoutError):
+                    e = exceptions.MessagingTimeout()
+                raise e
+
         else:
             self._do_send(
                 exchange=exchange, routing_key=queue, msg_dict=msg_dict,
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index d34a9c11a..a6dd7e093 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -111,8 +111,12 @@ class PikaPoller(object):
                 try:
                     if self._channel is None:
                         self._reconnect()
+                    # we need some time_limit here, not too small to avoid a
+                    # lot of not needed iterations but not too large to release
+                    # lock time to time and give a chance to perform another
+                    # method waiting this lock
                     self._connection.process_data_events(
-                        time_limit=timeout
+                        time_limit=0.25
                     )
                 except Exception:
                     self._cleanup()
@@ -183,18 +187,17 @@ class RpcServicePikaPoller(PikaPoller):
             self._pika_engine.declare_queue_binding_by_channel(
                 channel=self._channel, exchange=exchange, queue=queue,
                 routing_key=queue, exchange_type='direct', durable=False,
-                queue_expiration=queue_expiration, queue_auto_delete=False
+                queue_expiration=queue_expiration
             )
             self._pika_engine.declare_queue_binding_by_channel(
                 channel=self._channel, exchange=exchange, queue=server_queue,
                 routing_key=server_queue, exchange_type='direct',
-                queue_expiration=queue_expiration, queue_auto_delete=False,
-                durable=False
+                queue_expiration=queue_expiration, durable=False
             )
             self._pika_engine.declare_queue_binding_by_channel(
                 channel=self._channel, exchange=fanout_exchange, durable=False,
                 queue=server_queue, routing_key="", exchange_type='fanout',
-                queue_expiration=queue_expiration, queue_auto_delete=False,
+                queue_expiration=queue_expiration
             )
         self._queues_to_consume = queues_to_consume
 
@@ -225,7 +228,7 @@ class RpcReplyPikaPoller(PikaPoller):
             channel=self._channel,
             exchange=self._exchange, queue=self._queue,
             routing_key=self._queue, exchange_type='direct',
-            queue_expiration=queue_expiration, queue_auto_delete=False,
+            queue_expiration=queue_expiration,
             durable=False
         )
 
@@ -282,7 +285,6 @@ class NotificationPikaPoller(PikaPoller):
                 routing_key=routing_key,
                 exchange_type='direct',
                 queue_expiration=None,
-                queue_auto_delete=False,
                 durable=self._pika_engine.notification_persistence,
             )
             queues_to_consume[queue] = False

From bbf0efa29e5b5aede406f1e1f52f2e34941e9e11 Mon Sep 17 00:00:00 2001
From: dukhlov <dukhlov@mirantis.com>
Date: Thu, 3 Dec 2015 05:51:00 -0500
Subject: [PATCH 11/16] Preparations for configurable serialization

This patch moves message envelope logic to driver level
and separates serialization logic from envelop logic

Change-Id: I33c52193357fe298d82b1eb36c9b95edf7e500a4
---
 oslo_messaging/_drivers/impl_pika.py          |  13 +-
 .../_drivers/pika_driver/pika_engine.py       |  10 +-
 .../_drivers/pika_driver/pika_exceptions.py   |   7 +
 .../_drivers/pika_driver/pika_message.py      | 137 +++++++++++++++---
 .../_drivers/pika_driver/pika_poller.py       |   2 +-
 5 files changed, 134 insertions(+), 35 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index 4ccf346d4..a017e5690 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -15,8 +15,6 @@
 from oslo_config import cfg
 from oslo_log import log as logging
 
-from oslo_messaging._drivers import common
-
 from oslo_messaging._drivers.pika_driver import pika_engine as pika_drv_engine
 from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
 from oslo_messaging._drivers.pika_driver import pika_listener as pika_drv_lstnr
@@ -149,7 +147,7 @@ class PikaDriver(object):
         self._allowed_remote_exmods = allowed_remote_exmods
 
         self._pika_engine = pika_drv_engine.PikaEngine(
-            conf, url, default_exchange
+            conf, url, default_exchange, allowed_remote_exmods
         )
         self._reply_listener = pika_drv_lstnr.RpcReplyPikaListener(
             self._pika_engine
@@ -192,13 +190,10 @@ class PikaDriver(object):
         )
 
         if reply is not None:
-            if reply.message['failure']:
-                ex = common.deserialize_remote_exception(
-                    reply.message['failure'], self._allowed_remote_exmods
-                )
-                raise ex
+            if reply.failure is not None:
+                raise reply.failure
 
-            return reply.message['result']
+            return reply.result
 
     def _declare_notification_queue_binding(self, target, timeout=None):
         if timeout is not None and timeout < 0:
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index f8060cc13..88e90c0b0 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -21,6 +21,7 @@ from pika import credentials as pika_credentials
 
 import pika_pool
 
+import six
 import socket
 import sys
 
@@ -29,6 +30,8 @@ import time
 
 LOG = logging.getLogger(__name__)
 
+_EXCEPTIONS_MODULE = 'exceptions' if six.PY2 else 'builtins'
+
 
 def _is_eventlet_monkey_patched(module):
     """Determines safely is eventlet patching for module enabled or not
@@ -91,7 +94,8 @@ class PikaEngine(object):
     # (it should be defined in 'select' module of standard library in future)
     TCP_USER_TIMEOUT = 18
 
-    def __init__(self, conf, url, default_exchange=None):
+    def __init__(self, conf, url, default_exchange=None,
+                 allowed_remote_exmods=None):
         self.conf = conf
 
         self._force_select_poller_use = _is_eventlet_monkey_patched('select')
@@ -108,6 +112,10 @@ class PikaEngine(object):
             default_exchange
         )
 
+        self.allowed_remote_exmods = [_EXCEPTIONS_MODULE]
+        if allowed_remote_exmods:
+            self.allowed_remote_exmods.extend(allowed_remote_exmods)
+
         self.rpc_listener_prefetch_count = (
             conf.oslo_messaging_pika.rpc_listener_prefetch_count
         )
diff --git a/oslo_messaging/_drivers/pika_driver/pika_exceptions.py b/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
index 89e191560..c32d7e401 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_exceptions.py
@@ -59,3 +59,10 @@ class HostConnectionNotAllowedException(EstablishConnectionException):
     not allowed host (because of reconnection policy for example)
     """
     pass
+
+
+class UnsupportedDriverVersion(exceptions.MessagingException):
+    """Is raised when message is received but was sent by different,
+    not supported driver version
+    """
+    pass
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index 60f073c72..2fe4acaed 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -11,38 +11,66 @@
 #    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 socket
+import time
+import traceback
+import uuid
+
 from concurrent import futures
-
 from oslo_log import log as logging
-
-from oslo_messaging._drivers import common
-from oslo_messaging import exceptions
-
-from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
-
 from oslo_serialization import jsonutils
-
+from oslo_utils import importutils
 from pika import exceptions as pika_exceptions
 from pika import spec as pika_spec
 import pika_pool
-
 import retrying
 import six
-import socket
-import time
-import uuid
+
+
+import oslo_messaging
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
+from oslo_messaging import _utils as utils
+from oslo_messaging import exceptions
+
 
 LOG = logging.getLogger(__name__)
 
+_VERSION_HEADER = "version"
+_VERSION = "1.0"
+
+
+class RemoteExceptionMixin(object):
+    def __init__(self, module, clazz, message, trace):
+        self.module = module
+        self.clazz = clazz
+        self.message = message
+        self.trace = trace
+
+        self._str_msgs = message + "\n" + "\n".join(trace)
+
+    def __str__(self):
+        return self._str_msgs
+
 
 class PikaIncomingMessage(object):
 
     def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        headers = getattr(properties, "headers", {})
+        version = headers.get(_VERSION_HEADER, None)
+        if not utils.version_is_compatible(version, _VERSION):
+            raise pika_drv_exc.UnsupportedDriverVersion(
+                "Message's version: {} is not compatible with driver version: "
+                "{}".format(version, _VERSION))
+
         self._pika_engine = pika_engine
         self._no_ack = no_ack
         self._channel = channel
         self.delivery_tag = method.delivery_tag
 
+        self.version = version
+
         self.content_type = getattr(properties, "content_type",
                                     "application/json")
         self.content_encoding = getattr(properties, "content_encoding",
@@ -61,9 +89,7 @@ class PikaIncomingMessage(object):
                 )
             )
 
-        message_dict = common.deserialize_msg(
-            jsonutils.loads(body, encoding=self.content_encoding)
-        )
+        message_dict = jsonutils.loads(body, encoding=self.content_encoding)
 
         context_dict = {}
 
@@ -101,15 +127,37 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
         if not (self.msg_id and self.reply_q):
             return
 
-        if failure:
-            failure = common.serialize_remote_exception(failure, log_failure)
-
         msg = {
-            'result': reply,
-            'failure': failure,
             '_msg_id': self.msg_id,
         }
 
+        if failure is not None:
+            if isinstance(failure, RemoteExceptionMixin):
+                failure_data = {
+                    'class': failure.clazz,
+                    'module': failure.module,
+                    'message': failure.message,
+                    'tb': failure.trace
+                }
+            else:
+                tb = traceback.format_exception(*failure)
+                failure = failure[1]
+
+                cls_name = six.text_type(failure.__class__.__name__)
+                mod_name = six.text_type(failure.__class__.__module__)
+
+                failure_data = {
+                    'class': cls_name,
+                    'module': mod_name,
+                    'message': six.text_type(failure),
+                    'tb': tb
+                }
+
+            msg['_failure'] = failure_data
+
+        if reply is not None:
+            msg['_result'] = reply
+
         reply_outgoing_message = PikaOutgoingMessage(
             self._pika_engine, msg, self.ctxt, content_type=self.content_type,
             content_encoding=self.content_encoding
@@ -154,6 +202,49 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
             )
 
 
+class RpcReplyPikaIncomingMessage(PikaIncomingMessage):
+    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        self.result = None
+        self.failure = None
+
+        super(RpcReplyPikaIncomingMessage, self).__init__(
+            pika_engine, channel, method, properties, body, no_ack
+        )
+
+        if self.failure is not None:
+            trace = self.failure.get('tb', [])
+            message = self.failure.get('message', "")
+            class_name = self.failure.get('class')
+            module_name = self.failure.get('module')
+
+            res_exc = None
+
+            if module_name in pika_engine.allowed_remote_exmods:
+                try:
+                    module = importutils.import_module(module_name)
+                    klass = getattr(module, class_name)
+
+                    ex_type = type(
+                        klass.__name__,
+                        (RemoteExceptionMixin, klass),
+                        {}
+                    )
+
+                    res_exc = ex_type(module_name, class_name, message, trace)
+                except ImportError as e:
+                    LOG.warn(
+                        "Can not deserialize remote exception [module:{}, "
+                        "class:{}]. {}".format(module_name, class_name, str(e))
+                    )
+
+            # if we have not processed failure yet, use RemoteError class
+            if res_exc is None:
+                res_exc = oslo_messaging.RemoteError(
+                    class_name, message, trace
+                )
+            self.failure = res_exc
+
+
 class PikaOutgoingMessage(object):
     def __init__(self, pika_engine, message, context,
                  content_type="application/json", content_encoding="utf-8"):
@@ -253,16 +344,14 @@ class PikaOutgoingMessage(object):
         properties = pika_spec.BasicProperties(
             content_encoding=self.content_encoding,
             content_type=self.content_type,
+            headers={_VERSION_HEADER: _VERSION},
             delivery_mode=2 if persistent else 1
         )
 
         pool = (self._pika_engine.connection_with_confirmation_pool
                 if confirm else self._pika_engine.connection_pool)
 
-        body = jsonutils.dumps(
-            common.serialize_msg(msg_dict),
-            encoding=self.content_encoding
-        )
+        body = jsonutils.dumps(msg_dict, encoding=self.content_encoding)
 
         LOG.debug(
             "Sending message:[body:{}; properties: {}] to target: "
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index a6dd7e093..8d1b1f58b 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -255,7 +255,7 @@ class RpcReplyPikaPoller(PikaPoller):
         msg = super(RpcReplyPikaPoller, self).poll(timeout)
         if msg is None:
             return None
-        return pika_drv_msg.PikaIncomingMessage(
+        return pika_drv_msg.RpcReplyPikaIncomingMessage(
             self._pika_engine, *msg
         )
 

From 438a808c9150acec074499a70f6f2772c91dee8a Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Fri, 4 Dec 2015 18:45:46 +0200
Subject: [PATCH 12/16] Adds comment, updates pika-pool version

Change-Id: I3d9702bfdde89279258dbf0af027fa2a4a044edd
---
 .../_drivers/pika_driver/pika_message.py      | 160 ++++++++++++++++++
 requirements.txt                              |   2 +-
 2 files changed, 161 insertions(+), 1 deletion(-)

diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index 2fe4acaed..05f6fcd17 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -42,7 +42,20 @@ _VERSION = "1.0"
 
 
 class RemoteExceptionMixin(object):
+    """Used for constructing dynamic exception type during deserialization of
+    remote exception. It defines unified '__init__' method signature and
+    exception message format
+    """
     def __init__(self, module, clazz, message, trace):
+        """Store serialized data
+        :param module: String, module name for importing original exception
+            class of serialized remote exception
+        :param clazz: String, original class name of serialized remote
+            exception
+        :param message: String, original message of serialized remote
+            exception
+        :param trace: String, original trace of serialized remote exception
+        """
         self.module = module
         self.clazz = clazz
         self.message = message
@@ -55,8 +68,23 @@ class RemoteExceptionMixin(object):
 
 
 class PikaIncomingMessage(object):
+    """Driver friendly adapter for received message. Extract message
+    information from RabbitMQ message and provide access to it
+    """
 
     def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        """Parse RabbitMQ message
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param channel: Channel, RabbitMQ channel which was used for
+            this message delivery
+        :param method: Method, RabbitMQ message method
+        :param properties: Properties, RabbitMQ message properties
+        :param body: Bytes, RabbitMQ message body
+        :param no_ack: Boolean, defines should this message be acked by
+            consumer or not
+        """
         headers = getattr(properties, "headers", {})
         version = headers.get(_VERSION_HEADER, None)
         if not utils.version_is_compatible(version, _VERSION):
@@ -105,17 +133,43 @@ class PikaIncomingMessage(object):
         self.ctxt = context_dict
 
     def acknowledge(self):
+        """Ack the message. Should be called by message processing logic when
+        it considered as consumed (means that we don't need redelivery of this
+        message anymore)
+        """
         if not self._no_ack:
             self._channel.basic_ack(delivery_tag=self.delivery_tag)
 
     def requeue(self):
+        """Rollback the message. Should be called by message processing logic
+        when it can not process the message right now and should be redelivered
+        later if it is possible
+        """
         if not self._no_ack:
             return self._channel.basic_nack(delivery_tag=self.delivery_tag,
                                             requeue=True)
 
 
 class RpcPikaIncomingMessage(PikaIncomingMessage):
+    """PikaIncomingMessage implementation for RPC messages. It expects
+    extra RPC related fields in message body (msg_id and reply_q). Also 'reply'
+    method added to allow consumer to send RPC reply back to the RPC client
+    """
+
     def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        """Defines default values of msg_id and reply_q fields and just call
+        super.__init__ method
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param channel: Channel, RabbitMQ channel which was used for
+            this message delivery
+        :param method: Method, RabbitMQ message method
+        :param properties: Properties, RabbitMQ message properties
+        :param body: Bytes, RabbitMQ message body
+        :param no_ack: Boolean, defines should this message be acked by
+            consumer or not
+        """
         self.msg_id = None
         self.reply_q = None
 
@@ -124,6 +178,13 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
         )
 
     def reply(self, reply=None, failure=None, log_failure=True):
+        """Send back reply to the RPC client
+        :param reply - Dictionary, reply. In case of exception should be None
+        :param failure - Exception, exception, raised during processing RPC
+            request. Should be None if RPC request was successfully processed
+        :param log_failure, Boolean, not used in this implementation.
+            It present here to be compatible with driver API
+        """
         if not (self.msg_id and self.reply_q):
             return
 
@@ -203,7 +264,24 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
 
 
 class RpcReplyPikaIncomingMessage(PikaIncomingMessage):
+    """PikaIncomingMessage implementation for RPC reply messages. It expects
+    extra RPC reply related fields in message body (result and failure).
+    """
     def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+        """Defines default values of result and failure fields, call
+        super.__init__ method and then construct Exception object if failure is
+        not None
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param channel: Channel, RabbitMQ channel which was used for
+            this message delivery
+        :param method: Method, RabbitMQ message method
+        :param properties: Properties, RabbitMQ message properties
+        :param body: Bytes, RabbitMQ message body
+        :param no_ack: Boolean, defines should this message be acked by
+            consumer or not
+        """
         self.result = None
         self.failure = None
 
@@ -246,8 +324,23 @@ class RpcReplyPikaIncomingMessage(PikaIncomingMessage):
 
 
 class PikaOutgoingMessage(object):
+    """Driver friendly adapter for sending message. Construct RabbitMQ message
+    and send it
+    """
+
     def __init__(self, pika_engine, message, context,
                  content_type="application/json", content_encoding="utf-8"):
+        """Parse RabbitMQ message
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param message: Dictionary, user's message fields
+        :param context: Dictionary, request context's fields
+        :param content_type: String, content-type header, defines serialization
+            mechanism
+        :param content_encoding: String, defines encoding for text data
+        """
+
         self._pika_engine = pika_engine
 
         self.content_type = content_type
@@ -267,6 +360,17 @@ class PikaOutgoingMessage(object):
         self.unique_id = uuid.uuid4().hex
 
     def _prepare_message_to_send(self):
+        """Combine user's message fields an system fields (_unique_id,
+        context's data etc)
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param message: Dictionary, user's message fields
+        :param context: Dictionary, request context's fields
+        :param content_type: String, content-type header, defines serialization
+            mechanism
+        :param content_encoding: String, defines encoding for text data
+        """
         msg = self.message.copy()
 
         msg['_unique_id'] = self.unique_id
@@ -279,6 +383,20 @@ class PikaOutgoingMessage(object):
     @staticmethod
     def _publish(pool, exchange, routing_key, body, properties, mandatory,
                  expiration_time):
+        """Execute pika publish method using connection from connection pool
+        Also this message catches all pika related exceptions and raise
+        oslo.messaging specific exceptions
+
+        :param pool: Pool, pika connection pool for connection choosing
+        :param exchange: String, RabbitMQ exchange name for message sending
+        :param routing_key: String, RabbitMQ routing key for message routing
+        :param body: Bytes, RabbitMQ message payload
+        :param properties: Properties, RabbitMQ message properties
+        :param mandatory: Boolean, RabbitMQ publish mandatory flag (raise
+            exception if it is not possible to deliver message to any queue)
+        :param expiration_time: Float, expiration time in seconds
+            (like time.time())
+        """
         timeout = (None if expiration_time is None else
                    expiration_time - time.time())
         if timeout is not None and timeout < 0:
@@ -341,6 +459,21 @@ class PikaOutgoingMessage(object):
     def _do_send(self, exchange, routing_key, msg_dict, confirm=True,
                  mandatory=True, persistent=False, expiration_time=None,
                  retrier=None):
+        """Send prepared message with configured retrying
+
+        :param exchange: String, RabbitMQ exchange name for message sending
+        :param routing_key: String, RabbitMQ routing key for message routing
+        :param msg_dict: Dictionary, message payload
+        :param confirm: Boolean, enable publisher confirmation if True
+        :param mandatory: Boolean, RabbitMQ publish mandatory flag (raise
+            exception if it is not possible to deliver message to any queue)
+        :param persistent: Boolean, send persistent message if True, works only
+            for routing into durable queues
+        :param expiration_time: Float, expiration time in seconds
+            (like time.time())
+        :param retrier: retrying.Retrier, configured retrier object for sending
+            message, if None no retrying is performed
+        """
         properties = pika_spec.BasicProperties(
             content_encoding=self.content_encoding,
             content_type=self.content_type,
@@ -368,6 +501,20 @@ class PikaOutgoingMessage(object):
 
     def send(self, exchange, routing_key='', confirm=True, mandatory=True,
              persistent=False, expiration_time=None, retrier=None):
+        """Send message with configured retrying
+
+        :param exchange: String, RabbitMQ exchange name for message sending
+        :param routing_key: String, RabbitMQ routing key for message routing
+        :param confirm: Boolean, enable publisher confirmation if True
+        :param mandatory: Boolean, RabbitMQ publish mandatory flag (raise
+            exception if it is not possible to deliver message to any queue)
+        :param persistent: Boolean, send persistent message if True, works only
+            for routing into durable queues
+        :param expiration_time: Float, expiration time in seconds
+            (like time.time())
+        :param retrier: retrying.Retrier, configured retrier object for sending
+            message, if None no retrying is performed
+        """
         msg_dict = self._prepare_message_to_send()
 
         return self._do_send(exchange, routing_key, msg_dict, confirm,
@@ -375,6 +522,9 @@ class PikaOutgoingMessage(object):
 
 
 class RpcPikaOutgoingMessage(PikaOutgoingMessage):
+    """PikaOutgoingMessage implementation for RPC messages. It adds
+    possibility to wait and receive RPC reply
+    """
     def __init__(self, pika_engine, message, context,
                  content_type="application/json", content_encoding="utf-8"):
         super(RpcPikaOutgoingMessage, self).__init__(
@@ -385,6 +535,16 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
 
     def send(self, target, reply_listener=None, expiration_time=None,
              retrier=None):
+        """Send RPC message with configured retrying
+
+        :param target: Target, oslo.messaging target which defines RPC service
+        :param reply_listener: RpcReplyPikaListener, listener for waiting
+            reply. If None - return immediately without reply waiting
+        :param expiration_time: Float, expiration time in seconds
+            (like time.time())
+        :param retrier: retrying.Retrier, configured retrier object for sending
+            message, if None no retrying is performed
+        """
 
         exchange = self._pika_engine.get_rpc_exchange_name(
             target.exchange, target.topic, target.fanout, retrier is None
diff --git a/requirements.txt b/requirements.txt
index 681d55ec9..fa33c2437 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -34,7 +34,7 @@ PyYAML>=3.1.0
 amqp>=1.4.0
 kombu>=3.0.7
 pika>=0.10.0
-pika-pool>=0.1.2
+pika-pool>=0.1.3
 
 # middleware
 oslo.middleware>=2.8.0 # Apache-2.0

From bee303cf6f302ede14dd13c26d063cbe607e270c Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Wed, 9 Dec 2015 14:09:13 +0200
Subject: [PATCH 13/16] Adds comment for pika_pooler.py

Also this patch:
1) fixed import's order
2) removes PikaDriverCompatibleWithOldRabbit
     (tests, show that after retry implementation all works fine)
Change-Id: Ib34f6db569cadb5c27d8865f13ba32ef9a6c73e9
---
 oslo_messaging/_drivers/impl_pika.py          |  37 +----
 .../_drivers/pika_driver/pika_engine.py       |  16 +-
 .../_drivers/pika_driver/pika_listener.py     |  17 +-
 .../_drivers/pika_driver/pika_message.py      |   5 +-
 .../_drivers/pika_driver/pika_poller.py       | 154 +++++++++++++++++-
 setup.cfg                                     |   2 +-
 6 files changed, 170 insertions(+), 61 deletions(-)

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index a017e5690..eaa4a7685 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -12,8 +12,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import time
+
 from oslo_config import cfg
 from oslo_log import log as logging
+from oslo_messaging import exceptions
+import pika_pool
+import retrying
 
 from oslo_messaging._drivers.pika_driver import pika_engine as pika_drv_engine
 from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
@@ -21,13 +26,6 @@ from oslo_messaging._drivers.pika_driver import pika_listener as pika_drv_lstnr
 from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
 from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
 
-from oslo_messaging import exceptions
-
-import pika_pool
-
-import retrying
-import time
-
 LOG = logging.getLogger(__name__)
 
 pika_opts = [
@@ -144,7 +142,6 @@ class PikaDriver(object):
         conf.register_opts(notification_opts, group=opt_group)
 
         self.conf = conf
-        self._allowed_remote_exmods = allowed_remote_exmods
 
         self._pika_engine = pika_drv_engine.PikaEngine(
             conf, url, default_exchange, allowed_remote_exmods
@@ -277,27 +274,3 @@ class PikaDriver(object):
 
     def cleanup(self):
         self._reply_listener.cleanup()
-
-
-class PikaDriverCompatibleWithRabbitDriver(PikaDriver):
-    """Old RabbitMQ driver creates exchange before sending message.
-    In this case if no rpc service listen this exchange message will be sent
-    to /dev/null but client will know anything about it. That is strange.
-    But for now we need to keep original behaviour
-    """
-    def send(self, target, ctxt, message, wait_for_reply=None, timeout=None,
-             retry=None):
-        try:
-            return super(PikaDriverCompatibleWithRabbitDriver, self).send(
-                target=target,
-                ctxt=ctxt,
-                message=message,
-                wait_for_reply=wait_for_reply,
-                timeout=timeout,
-                retry=retry
-            )
-        except exceptions.MessageDeliveryFailure:
-            if wait_for_reply:
-                raise exceptions.MessagingTimeout()
-            else:
-                return None
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 88e90c0b0..06dfcdbf1 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -11,22 +11,20 @@
 #    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 socket
+import sys
+import threading
+import time
+
 from oslo_log import log as logging
-
-from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
-
 import pika
 from pika.adapters import select_connection
 from pika import credentials as pika_credentials
-
 import pika_pool
-
 import six
-import socket
-import sys
 
-import threading
-import time
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
 
 LOG = logging.getLogger(__name__)
 
diff --git a/oslo_messaging/_drivers/pika_driver/pika_listener.py b/oslo_messaging/_drivers/pika_driver/pika_listener.py
index cfbb5b8de..8eff1feb7 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_listener.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_listener.py
@@ -12,15 +12,15 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from oslo_log import log as logging
-
-from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
-from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
-
 import threading
 import time
 import uuid
 
+from concurrent import futures
+from oslo_log import log as logging
+
+from oslo_messaging._drivers.pika_driver import pika_exceptions as pika_drv_exc
+from oslo_messaging._drivers.pika_driver import pika_poller as pika_drv_poller
 
 LOG = logging.getLogger(__name__)
 
@@ -111,13 +111,16 @@ class RpcReplyPikaListener(object):
             except BaseException:
                 LOG.exception("Unexpected exception during reply polling")
 
-    def register_reply_waiter(self, msg_id, future):
+    def register_reply_waiter(self, msg_id):
         """Register reply waiter. Should be called before message sending to
         the server
         :param msg_id: String, message_id of expected reply
-        :param future: Future, container for expected reply to be returned over
+        :return future: Future, container for expected reply to be returned
+            over
         """
+        future = futures.Future()
         self._reply_waiting_futures[msg_id] = future
+        return future
 
     def unregister_reply_waiter(self, msg_id):
         """Unregister reply waiter. Should be called if client has not got
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index 05f6fcd17..9bf9febdb 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -564,11 +564,8 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
             msg_dict["_reply_q"] = reply_listener.get_reply_qname(
                 expiration_time - time.time()
             )
-            future = futures.Future()
 
-            reply_listener.register_reply_waiter(
-                msg_id=msg_id, future=future
-            )
+            future = reply_listener.register_reply_waiter(msg_id=msg_id)
 
             self._do_send(
                 exchange=exchange, routing_key=queue, msg_dict=msg_dict,
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index 8d1b1f58b..185c8d02a 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -13,21 +13,32 @@
 #    under the License.
 
 import collections
-
-from oslo_log import log as logging
-
 import threading
 import time
 
-from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
+from oslo_log import log as logging
 import pika_pool
 import retrying
 
+from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
+
 LOG = logging.getLogger(__name__)
 
 
 class PikaPoller(object):
+    """Provides user friendly functionality for RabbitMQ message consuming,
+    handles low level connectivity problems and restore connection if some
+    connectivity related problem detected
+    """
+
     def __init__(self, pika_engine, prefetch_count):
+        """Initialize required fields
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param prefetch_count: Integer, maximum count of unacknowledged
+            messages which RabbitMQ broker sends to this consumer
+        """
         self._pika_engine = pika_engine
 
         self._connection = None
@@ -43,6 +54,9 @@ class PikaPoller(object):
         self._message_queue = collections.deque()
 
     def _reconnect(self):
+        """Performs reconnection to the broker. It is unsafe method for
+        internal use only
+        """
         self._connection = self._pika_engine.create_connection(
             for_listening=True
         )
@@ -50,17 +64,31 @@ class PikaPoller(object):
         self._channel.basic_qos(prefetch_count=self._prefetch_count)
 
         if self._queues_to_consume is None:
-            self._declare_queue_binding()
+            self._queues_to_consume = self._declare_queue_binding()
 
         for queue, no_ack in self._queues_to_consume.iteritems():
             self._start_consuming(queue, no_ack)
 
     def _declare_queue_binding(self):
+        """Is called by recovering connection logic if target RabbitMQ
+        exchange and (or) queue do not exist. Should be overridden in child
+        classes
+
+        :return Dictionary, declared_queue_name -> no_ack_mode
+        """
         raise NotImplementedError(
             "It is base class. Please declare exchanges and queues here"
         )
 
     def _start_consuming(self, queue, no_ack):
+        """Is called by recovering connection logic for starting consumption
+        of the RabbitMQ queue
+
+        :param queue: String, RabbitMQ queue name for consuming
+        :param no_ack: Boolean, Choose consuming acknowledgement mode. If True,
+            acknowledges are not needed. RabbitMQ considers message consumed
+            after sending it to consumer immediately
+        """
         on_message_no_ack_callback = (
             self._on_message_no_ack_callback if no_ack
             else self._on_message_with_ack_callback
@@ -74,16 +102,25 @@ class PikaPoller(object):
             raise
 
     def _on_message_no_ack_callback(self, unused, method, properties, body):
+        """Is called by Pika when message was received from queue listened with
+        no_ack=True mode
+        """
         self._message_queue.append(
             (self._channel, method, properties, body, True)
         )
 
     def _on_message_with_ack_callback(self, unused, method, properties, body):
+        """Is called by Pika when message was received from queue listened with
+        no_ack=False mode
+        """
         self._message_queue.append(
             (self._channel, method, properties, body, False)
         )
 
     def _cleanup(self):
+        """Cleanup allocated resources (channel, connection, etc). It is unsafe
+        method for internal use only
+        """
         if self._channel:
             try:
                 self._channel.close()
@@ -101,6 +138,13 @@ class PikaPoller(object):
             self._connection = None
 
     def poll(self, timeout=None):
+        """Main method of this class - consumes message from RabbitMQ
+
+        :param: timeout: float, seconds, timeout for waiting new incoming
+            message, None means wait forever
+        :return: tuple, RabbitMQ message related data
+            (channel, method, properties, body, no_ack)
+        """
         expiration_time = time.time() + timeout if timeout else None
 
         while not self._message_queue:
@@ -129,9 +173,16 @@ class PikaPoller(object):
         return self._message_queue.popleft()
 
     def start(self):
+        """Starts poller. Should be called before polling to allow message
+        consuming
+        """
         self._started = True
 
     def stop(self):
+        """Stops poller. Should be called when polling is not needed anymore to
+        stop new message consuming. After that it is necessary to poll already
+        prefetched messages
+        """
         with self._lock:
             if not self._started:
                 return
@@ -140,6 +191,7 @@ class PikaPoller(object):
             self._cleanup()
 
     def reconnect(self):
+        """Safe version of _reconnect. Performs reconnection to the broker."""
         with self._lock:
             self._cleanup()
             try:
@@ -149,18 +201,39 @@ class PikaPoller(object):
                 raise
 
     def cleanup(self):
+        """Safe version of _cleanup. Cleans up allocated resources (channel,
+        connection, etc).
+        """
         with self._lock:
             self._cleanup()
 
 
 class RpcServicePikaPoller(PikaPoller):
+    """PikaPoller implementation for polling RPC messages. Overrides base
+    functionality according to RPC specific
+    """
     def __init__(self, pika_engine, target, prefetch_count):
+        """Adds target parameter for declaring RPC specific exchanges and
+        queues
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param target: Target, oslo.messaging Target object which defines RPC
+            endpoint
+        :param prefetch_count: Integer, maximum count of unacknowledged
+            messages which RabbitMQ broker sends to this consumer
+        """
         self._target = target
 
         super(RpcServicePikaPoller, self).__init__(
             pika_engine, prefetch_count=prefetch_count)
 
     def _declare_queue_binding(self):
+        """Overrides base method and perform declaration of RabbitMQ exchanges
+        and queues which correspond to oslo.messaging RPC target
+
+        :return Dictionary, declared_queue_name -> no_ack_mode
+        """
         queue_expiration = (
             self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
         )
@@ -199,9 +272,16 @@ class RpcServicePikaPoller(PikaPoller):
                 queue=server_queue, routing_key="", exchange_type='fanout',
                 queue_expiration=queue_expiration
             )
-        self._queues_to_consume = queues_to_consume
+        return queues_to_consume
 
     def poll(self, timeout=None):
+        """Overrides base method and wrap RabbitMQ message into
+        RpcPikaIncomingMessage
+
+        :param: timeout: float, seconds, timeout for waiting new incoming
+            message, None means wait forever
+        :return: RpcPikaIncomingMessage, consumed RPC message
+        """
         msg = super(RpcServicePikaPoller, self).poll(timeout)
         if msg is None:
             return None
@@ -211,7 +291,20 @@ class RpcServicePikaPoller(PikaPoller):
 
 
 class RpcReplyPikaPoller(PikaPoller):
+    """PikaPoller implementation for polling RPC reply messages. Overrides
+    base functionality according to RPC reply specific
+    """
     def __init__(self, pika_engine, exchange, queue, prefetch_count):
+        """Adds exchange and queue parameter for declaring exchange and queue
+        used for RPC reply delivery
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param exchange: String, exchange name used for RPC reply delivery
+        :param queue: String, queue name used for RPC reply delivery
+        :param prefetch_count: Integer, maximum count of unacknowledged
+            messages which RabbitMQ broker sends to this consumer
+        """
         self._exchange = exchange
         self._queue = queue
 
@@ -220,6 +313,11 @@ class RpcReplyPikaPoller(PikaPoller):
         )
 
     def _declare_queue_binding(self):
+        """Overrides base method and perform declaration of RabbitMQ exchange
+        and queue used for RPC reply delivery
+
+        :return Dictionary, declared_queue_name -> no_ack_mode
+        """
         queue_expiration = (
             self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
         )
@@ -232,9 +330,15 @@ class RpcReplyPikaPoller(PikaPoller):
             durable=False
         )
 
-        self._queues_to_consume = {self._queue: False}
+        return {self._queue: False}
 
     def start(self, timeout=None):
+        """Overrides default behaviour of start method. Base start method
+        does not create connection to RabbitMQ during start method (uses
+        lazy connecting during first poll method call). This class should be
+        connected after start call to ensure that exchange and queue for reply
+        delivery are created before RPC request sending
+        """
         super(RpcReplyPikaPoller, self).start()
 
         def on_exception(ex):
@@ -252,6 +356,13 @@ class RpcReplyPikaPoller(PikaPoller):
         retrier(self.reconnect)()
 
     def poll(self, timeout=None):
+        """Overrides base method and wrap RabbitMQ message into
+        RpcReplyPikaIncomingMessage
+
+        :param: timeout: float, seconds, timeout for waiting new incoming
+            message, None means wait forever
+        :return: RpcReplyPikaIncomingMessage, consumed RPC reply message
+        """
         msg = super(RpcReplyPikaPoller, self).poll(timeout)
         if msg is None:
             return None
@@ -261,8 +372,23 @@ class RpcReplyPikaPoller(PikaPoller):
 
 
 class NotificationPikaPoller(PikaPoller):
+    """PikaPoller implementation for polling Notification messages. Overrides
+    base functionality according to Notification specific
+    """
     def __init__(self, pika_engine, targets_and_priorities,
                  queue_name=None, prefetch_count=100):
+        """Adds exchange and queue parameter for declaring exchange and queue
+        used for RPC reply delivery
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param targets_and_priorities: list of (target, priority), defines
+            default queue names for corresponding notification types
+        :param queue: String, alternative queue name used for this poller
+            instead of default queue name
+        :param prefetch_count: Integer, maximum count of unacknowledged
+            messages which RabbitMQ broker sends to this consumer
+        """
         self._targets_and_priorities = targets_and_priorities
         self._queue_name = queue_name
 
@@ -271,6 +397,11 @@ class NotificationPikaPoller(PikaPoller):
         )
 
     def _declare_queue_binding(self):
+        """Overrides base method and perform declaration of RabbitMQ exchanges
+        and queues used for notification delivery
+
+        :return Dictionary, declared_queue_name -> no_ack_mode
+        """
         queues_to_consume = {}
         for target, priority in self._targets_and_priorities:
             routing_key = '%s.%s' % (target.topic, priority)
@@ -289,9 +420,16 @@ class NotificationPikaPoller(PikaPoller):
             )
             queues_to_consume[queue] = False
 
-        self._queues_to_consume = queues_to_consume
+        return queues_to_consume
 
     def poll(self, timeout=None):
+        """Overrides base method and wrap RabbitMQ message into
+        PikaIncomingMessage
+
+        :param: timeout: float, seconds, timeout for waiting new incoming
+            message, None means wait forever
+        :return: PikaIncomingMessage, consumed Notification message
+        """
         msg = super(NotificationPikaPoller, self).poll(timeout)
         if msg is None:
             return None
diff --git a/setup.cfg b/setup.cfg
index 1524e0467..d5b4b08bc 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -35,7 +35,7 @@ oslo.messaging.drivers =
 
     # This is just for internal testing
     fake = oslo_messaging._drivers.impl_fake:FakeDriver
-    pika = oslo_messaging._drivers.impl_pika:PikaDriverCompatibleWithRabbitDriver
+    pika = oslo_messaging._drivers.impl_pika:PikaDriver
 
 oslo.messaging.executors =
     aioeventlet = oslo_messaging._executors.impl_aioeventlet:AsyncioEventletExecutor

From 3976a2ff81408b7f86e898eb0a87634a3f9ed2c0 Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Mon, 14 Dec 2015 18:49:50 +0200
Subject: [PATCH 14/16] Fixes conflicts after merging master

Change-Id: I0d75c19e3002a3aad2dd35bbaea203fa9ba0c0ea
---
 .../_drivers/pika_driver/pika_listener.py     | 30 ++++--
 .../_drivers/pika_driver/pika_poller.py       | 94 +++++++------------
 2 files changed, 52 insertions(+), 72 deletions(-)

diff --git a/oslo_messaging/_drivers/pika_driver/pika_listener.py b/oslo_messaging/_drivers/pika_driver/pika_listener.py
index 8eff1feb7..2c33168e5 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_listener.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_listener.py
@@ -97,17 +97,27 @@ class RpcReplyPikaListener(object):
         """
         while self._reply_poller:
             try:
-                message = self._reply_poller.poll()
-                if message is None:
+                try:
+                    messages = self._reply_poller.poll()
+                except pika_drv_exc.EstablishConnectionException:
+                    LOG.exception("Problem during establishing connection for "
+                                  "reply polling")
+                    time.sleep(
+                        self._pika_engine.host_connection_reconnect_delay
+                    )
                     continue
-                message.acknowledge()
-                future = self._reply_waiting_futures.pop(message.msg_id, None)
-                if future is not None:
-                    future.set_result(message)
-            except pika_drv_exc.EstablishConnectionException:
-                LOG.exception("Problem during establishing connection for "
-                              "reply polling")
-                time.sleep(self._pika_engine.host_connection_reconnect_delay)
+
+                for message in messages:
+                    try:
+                        message.acknowledge()
+                        future = self._reply_waiting_futures.pop(
+                            message.msg_id, None
+                        )
+                        if future is not None:
+                            future.set_result(message)
+                    except Exception:
+                        LOG.exception("Unexpected exception during processing"
+                                      "reply message")
             except BaseException:
                 LOG.exception("Unexpected exception during reply polling")
 
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index 185c8d02a..1390ced75 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import collections
 import threading
 import time
 
@@ -31,27 +30,30 @@ class PikaPoller(object):
     connectivity related problem detected
     """
 
-    def __init__(self, pika_engine, prefetch_count):
+    def __init__(self, pika_engine, prefetch_count,
+                 incoming_message_class=pika_drv_msg.PikaIncomingMessage):
         """Initialize required fields
 
         :param pika_engine: PikaEngine, shared object with configuration and
             shared driver functionality
         :param prefetch_count: Integer, maximum count of unacknowledged
             messages which RabbitMQ broker sends to this consumer
+        :param incoming_message_class: PikaIncomingMessage, wrapper for
+            consumed RabbitMQ message
         """
         self._pika_engine = pika_engine
+        self._prefetch_count = prefetch_count
+        self._incoming_message_class = incoming_message_class
 
         self._connection = None
         self._channel = None
         self._lock = threading.Lock()
 
-        self._prefetch_count = prefetch_count
-
         self._started = False
 
         self._queues_to_consume = None
 
-        self._message_queue = collections.deque()
+        self._message_queue = []
 
     def _reconnect(self):
         """Performs reconnection to the broker. It is unsafe method for
@@ -106,7 +108,10 @@ class PikaPoller(object):
         no_ack=True mode
         """
         self._message_queue.append(
-            (self._channel, method, properties, body, True)
+            self._incoming_message_class(
+                self._pika_engine, self._channel, method, properties, body,
+                True
+            )
         )
 
     def _on_message_with_ack_callback(self, unused, method, properties, body):
@@ -114,7 +119,10 @@ class PikaPoller(object):
         no_ack=False mode
         """
         self._message_queue.append(
-            (self._channel, method, properties, body, False)
+            self._incoming_message_class(
+                self._pika_engine, self._channel, method, properties, body,
+                False
+            )
         )
 
     def _cleanup(self):
@@ -137,17 +145,19 @@ class PikaPoller(object):
                     LOG.exception("Unexpected error during closing connection")
             self._connection = None
 
-    def poll(self, timeout=None):
+    def poll(self, timeout=None, prefetch_size=1):
         """Main method of this class - consumes message from RabbitMQ
 
         :param: timeout: float, seconds, timeout for waiting new incoming
             message, None means wait forever
-        :return: tuple, RabbitMQ message related data
-            (channel, method, properties, body, no_ack)
+        :param: prefetch_size:  Integer, count of messages which we are want to
+            poll. It blocks until prefetch_size messages are consumed or until
+            timeout gets expired
+        :return: list of PikaIncomingMessage, RabbitMQ messages
         """
         expiration_time = time.time() + timeout if timeout else None
 
-        while not self._message_queue:
+        while len(self._message_queue) < prefetch_size:
             with self._lock:
                 if not self._started:
                     return None
@@ -162,15 +172,17 @@ class PikaPoller(object):
                     self._connection.process_data_events(
                         time_limit=0.25
                     )
-                except Exception:
+                except Exception as e:
+                    LOG.warn("Exception during consuming message. " + str(e))
                     self._cleanup()
-                    raise
             if timeout is not None:
                 timeout = expiration_time - time.time()
                 if timeout <= 0:
-                    return None
+                    break
 
-        return self._message_queue.popleft()
+        result = self._message_queue[:prefetch_size]
+        self._message_queue = self._message_queue[prefetch_size:]
+        return result
 
     def start(self):
         """Starts poller. Should be called before polling to allow message
@@ -226,7 +238,9 @@ class RpcServicePikaPoller(PikaPoller):
         self._target = target
 
         super(RpcServicePikaPoller, self).__init__(
-            pika_engine, prefetch_count=prefetch_count)
+            pika_engine, prefetch_count=prefetch_count,
+            incoming_message_class=pika_drv_msg.RpcPikaIncomingMessage
+        )
 
     def _declare_queue_binding(self):
         """Overrides base method and perform declaration of RabbitMQ exchanges
@@ -274,21 +288,6 @@ class RpcServicePikaPoller(PikaPoller):
             )
         return queues_to_consume
 
-    def poll(self, timeout=None):
-        """Overrides base method and wrap RabbitMQ message into
-        RpcPikaIncomingMessage
-
-        :param: timeout: float, seconds, timeout for waiting new incoming
-            message, None means wait forever
-        :return: RpcPikaIncomingMessage, consumed RPC message
-        """
-        msg = super(RpcServicePikaPoller, self).poll(timeout)
-        if msg is None:
-            return None
-        return pika_drv_msg.RpcPikaIncomingMessage(
-            self._pika_engine, *msg
-        )
-
 
 class RpcReplyPikaPoller(PikaPoller):
     """PikaPoller implementation for polling RPC reply messages. Overrides
@@ -309,7 +308,8 @@ class RpcReplyPikaPoller(PikaPoller):
         self._queue = queue
 
         super(RpcReplyPikaPoller, self).__init__(
-            pika_engine, prefetch_count
+            pika_engine=pika_engine, prefetch_count=prefetch_count,
+            incoming_message_class=pika_drv_msg.RpcReplyPikaIncomingMessage
         )
 
     def _declare_queue_binding(self):
@@ -355,21 +355,6 @@ class RpcReplyPikaPoller(PikaPoller):
 
         retrier(self.reconnect)()
 
-    def poll(self, timeout=None):
-        """Overrides base method and wrap RabbitMQ message into
-        RpcReplyPikaIncomingMessage
-
-        :param: timeout: float, seconds, timeout for waiting new incoming
-            message, None means wait forever
-        :return: RpcReplyPikaIncomingMessage, consumed RPC reply message
-        """
-        msg = super(RpcReplyPikaPoller, self).poll(timeout)
-        if msg is None:
-            return None
-        return pika_drv_msg.RpcReplyPikaIncomingMessage(
-            self._pika_engine, *msg
-        )
-
 
 class NotificationPikaPoller(PikaPoller):
     """PikaPoller implementation for polling Notification messages. Overrides
@@ -421,18 +406,3 @@ class NotificationPikaPoller(PikaPoller):
             queues_to_consume[queue] = False
 
         return queues_to_consume
-
-    def poll(self, timeout=None):
-        """Overrides base method and wrap RabbitMQ message into
-        PikaIncomingMessage
-
-        :param: timeout: float, seconds, timeout for waiting new incoming
-            message, None means wait forever
-        :return: PikaIncomingMessage, consumed Notification message
-        """
-        msg = super(NotificationPikaPoller, self).poll(timeout)
-        if msg is None:
-            return None
-        return pika_drv_msg.PikaIncomingMessage(
-            self._pika_engine, *msg
-        )

From 5149461fd2523a0c2afc187bd023784438f7b81a Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Mon, 14 Dec 2015 11:36:28 +0200
Subject: [PATCH 15/16] Adds tests for pika_message.py

Also small mistakes were fixed,
msg_id, unique_id and reply_q fields were
moved to corresponding AMQP properties

Change-Id: I5147c35c1a2ce0205e08ca81db164a3cc879fb0a
---
 oslo_messaging/_drivers/impl_pika.py          |   4 +-
 .../_drivers/pika_driver/pika_engine.py       |  12 +-
 .../_drivers/pika_driver/pika_message.py      | 261 ++++----
 .../_drivers/pika_driver/pika_poller.py       |   3 +-
 oslo_messaging/tests/drivers/pika/__init__.py |   0
 .../tests/drivers/pika/test_message.py        | 622 ++++++++++++++++++
 6 files changed, 781 insertions(+), 121 deletions(-)
 create mode 100644 oslo_messaging/tests/drivers/pika/__init__.py
 create mode 100644 oslo_messaging/tests/drivers/pika/test_message.py

diff --git a/oslo_messaging/_drivers/impl_pika.py b/oslo_messaging/_drivers/impl_pika.py
index eaa4a7685..3d633a5b1 100644
--- a/oslo_messaging/_drivers/impl_pika.py
+++ b/oslo_messaging/_drivers/impl_pika.py
@@ -198,8 +198,8 @@ class PikaDriver(object):
                 "Timeout for current operation was expired."
             )
         try:
-            with self._pika_engine.connection_pool.acquire(
-                    timeout=timeout) as conn:
+            with (self._pika_engine.connection_without_confirmation_pool
+                    .acquire)(timeout=timeout) as conn:
                 self._pika_engine.declare_queue_binding_by_channel(
                     conn.channel,
                     exchange=(
diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 06dfcdbf1..6e877bb2e 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -12,6 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import random
 import socket
 import sys
 import threading
@@ -44,7 +45,7 @@ def _is_eventlet_monkey_patched(module):
     return eventlet.patcher.is_monkey_patched(module)
 
 
-def _create__select_poller_connection_impl(
+def _create_select_poller_connection_impl(
         parameters, on_open_callback, on_open_error_callback,
         on_close_callback, stop_ioloop_on_close):
     """Used for disabling autochoise of poller ('select', 'poll', 'epool', etc)
@@ -198,7 +199,6 @@ class PikaEngine(object):
 
         self._connection_host_param_list = []
         self._connection_host_status_list = []
-        self._next_connection_host_num = 0
 
         for transport_host in url.hosts:
             pika_params = common_pika_params.copy()
@@ -215,9 +215,13 @@ class PikaEngine(object):
                 self.HOST_CONNECTION_LAST_SUCCESS_TRY_TIME: 0
             })
 
+        self._next_connection_host_num = random.randint(
+            0, len(self._connection_host_param_list) - 1
+        )
+
         # initializing 2 connection pools: 1st for connections without
         # confirmations, 2nd - with confirmations
-        self.connection_pool = pika_pool.QueuedPool(
+        self.connection_without_confirmation_pool = pika_pool.QueuedPool(
             create=self.create_connection,
             max_size=self.conf.oslo_messaging_pika.pool_max_size,
             max_overflow=self.conf.oslo_messaging_pika.pool_max_overflow,
@@ -336,7 +340,7 @@ class PikaEngine(object):
                         ),
                         **base_host_params
                     ),
-                    _impl_class=(_create__select_poller_connection_impl
+                    _impl_class=(_create_select_poller_connection_impl
                                  if self._force_select_poller_use else None)
                 )
 
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index 9bf9febdb..edd5c7328 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -95,40 +95,36 @@ class PikaIncomingMessage(object):
         self._pika_engine = pika_engine
         self._no_ack = no_ack
         self._channel = channel
-        self.delivery_tag = method.delivery_tag
+        self._delivery_tag = method.delivery_tag
 
-        self.version = version
+        self._version = version
 
-        self.content_type = getattr(properties, "content_type",
-                                    "application/json")
-        self.content_encoding = getattr(properties, "content_encoding",
-                                        "utf-8")
+        self._content_type = properties.content_type
+        self._content_encoding = properties.content_encoding
+        self.unique_id = properties.message_id
 
         self.expiration_time = (
             None if properties.expiration is None else
             time.time() + float(properties.expiration) / 1000
         )
 
-        if self.content_type != "application/json":
+        if self._content_type != "application/json":
             raise NotImplementedError(
                 "Content-type['{}'] is not valid, "
                 "'application/json' only is supported.".format(
-                    self.content_type
+                    self._content_type
                 )
             )
 
-        message_dict = jsonutils.loads(body, encoding=self.content_encoding)
+        message_dict = jsonutils.loads(body, encoding=self._content_encoding)
 
         context_dict = {}
 
         for key in list(message_dict.keys()):
             key = six.text_type(key)
-            if key.startswith('_context_'):
+            if key.startswith('_$_'):
                 value = message_dict.pop(key)
-                context_dict[key[9:]] = value
-            elif key.startswith('_'):
-                value = message_dict.pop(key)
-                setattr(self, key[1:], value)
+                context_dict[key[3:]] = value
         self.message = message_dict
         self.ctxt = context_dict
 
@@ -138,7 +134,7 @@ class PikaIncomingMessage(object):
         message anymore)
         """
         if not self._no_ack:
-            self._channel.basic_ack(delivery_tag=self.delivery_tag)
+            self._channel.basic_ack(delivery_tag=self._delivery_tag)
 
     def requeue(self):
         """Rollback the message. Should be called by message processing logic
@@ -146,7 +142,7 @@ class PikaIncomingMessage(object):
         later if it is possible
         """
         if not self._no_ack:
-            return self._channel.basic_nack(delivery_tag=self.delivery_tag,
+            return self._channel.basic_nack(delivery_tag=self._delivery_tag,
                                             requeue=True)
 
 
@@ -170,58 +166,30 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
         :param no_ack: Boolean, defines should this message be acked by
             consumer or not
         """
-        self.msg_id = None
-        self.reply_q = None
-
         super(RpcPikaIncomingMessage, self).__init__(
             pika_engine, channel, method, properties, body, no_ack
         )
+        self.reply_q = properties.reply_to
+        self.msg_id = properties.correlation_id
 
     def reply(self, reply=None, failure=None, log_failure=True):
         """Send back reply to the RPC client
-        :param reply - Dictionary, reply. In case of exception should be None
-        :param failure - Exception, exception, raised during processing RPC
-            request. Should be None if RPC request was successfully processed
-        :param log_failure, Boolean, not used in this implementation.
+        :param reply: Dictionary, reply. In case of exception should be None
+        :param failure: Tuple, should be a sys.exc_info() tuple.
+            Should be None if RPC request was successfully processed.
+        :param log_failure: Boolean, not used in this implementation.
             It present here to be compatible with driver API
+
+        :return RpcReplyPikaIncomingMessage, message with reply
         """
-        if not (self.msg_id and self.reply_q):
+
+        if self.reply_q is None:
             return
 
-        msg = {
-            '_msg_id': self.msg_id,
-        }
-
-        if failure is not None:
-            if isinstance(failure, RemoteExceptionMixin):
-                failure_data = {
-                    'class': failure.clazz,
-                    'module': failure.module,
-                    'message': failure.message,
-                    'tb': failure.trace
-                }
-            else:
-                tb = traceback.format_exception(*failure)
-                failure = failure[1]
-
-                cls_name = six.text_type(failure.__class__.__name__)
-                mod_name = six.text_type(failure.__class__.__module__)
-
-                failure_data = {
-                    'class': cls_name,
-                    'module': mod_name,
-                    'message': six.text_type(failure),
-                    'tb': tb
-                }
-
-            msg['_failure'] = failure_data
-
-        if reply is not None:
-            msg['_result'] = reply
-
-        reply_outgoing_message = PikaOutgoingMessage(
-            self._pika_engine, msg, self.ctxt, content_type=self.content_type,
-            content_encoding=self.content_encoding
+        reply_outgoing_message = RpcReplyPikaOutgoingMessage(
+            self._pika_engine, self.msg_id, reply=reply, failure_info=failure,
+            content_type=self._content_type,
+            content_encoding=self._content_encoding
         )
 
         def on_exception(ex):
@@ -242,11 +210,7 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
 
         try:
             reply_outgoing_message.send(
-                exchange=self._pika_engine.rpc_reply_exchange,
-                routing_key=self.reply_q,
-                confirm=True,
-                mandatory=False,
-                persistent=False,
+                reply_q=self.reply_q,
                 expiration_time=self.expiration_time,
                 retrier=retrier
             )
@@ -282,18 +246,20 @@ class RpcReplyPikaIncomingMessage(PikaIncomingMessage):
         :param no_ack: Boolean, defines should this message be acked by
             consumer or not
         """
-        self.result = None
-        self.failure = None
-
         super(RpcReplyPikaIncomingMessage, self).__init__(
             pika_engine, channel, method, properties, body, no_ack
         )
 
+        self.msg_id = properties.correlation_id
+
+        self.result = self.message.get("s", None)
+        self.failure = self.message.get("e", None)
+
         if self.failure is not None:
-            trace = self.failure.get('tb', [])
-            message = self.failure.get('message', "")
-            class_name = self.failure.get('class')
-            module_name = self.failure.get('module')
+            trace = self.failure.get('t', [])
+            message = self.failure.get('s', "")
+            class_name = self.failure.get('c')
+            module_name = self.failure.get('m')
 
             res_exc = None
 
@@ -343,14 +309,14 @@ class PikaOutgoingMessage(object):
 
         self._pika_engine = pika_engine
 
-        self.content_type = content_type
-        self.content_encoding = content_encoding
+        self._content_type = content_type
+        self._content_encoding = content_encoding
 
-        if self.content_type != "application/json":
+        if self._content_type != "application/json":
             raise NotImplementedError(
                 "Content-type['{}'] is not valid, "
                 "'application/json' only is supported.".format(
-                    self.content_type
+                    self._content_type
                 )
             )
 
@@ -362,23 +328,21 @@ class PikaOutgoingMessage(object):
     def _prepare_message_to_send(self):
         """Combine user's message fields an system fields (_unique_id,
         context's data etc)
-
-        :param pika_engine: PikaEngine, shared object with configuration and
-            shared driver functionality
-        :param message: Dictionary, user's message fields
-        :param context: Dictionary, request context's fields
-        :param content_type: String, content-type header, defines serialization
-            mechanism
-        :param content_encoding: String, defines encoding for text data
         """
         msg = self.message.copy()
 
-        msg['_unique_id'] = self.unique_id
+        if self.context:
+            for key, value in six.iteritems(self.context):
+                key = six.text_type(key)
+                msg['_$_' + key] = value
 
-        for key, value in self.context.iteritems():
-            key = six.text_type(key)
-            msg['_context_' + key] = value
-        return msg
+        props = pika_spec.BasicProperties(
+            content_encoding=self._content_encoding,
+            content_type=self._content_type,
+            headers={_VERSION_HEADER: _VERSION},
+            message_id=self.unique_id,
+        )
+        return msg, props
 
     @staticmethod
     def _publish(pool, exchange, routing_key, body, properties, mandatory,
@@ -456,14 +420,15 @@ class PikaOutgoingMessage(object):
                 "Socket timeout exceeded."
             )
 
-    def _do_send(self, exchange, routing_key, msg_dict, confirm=True,
-                 mandatory=True, persistent=False, expiration_time=None,
-                 retrier=None):
+    def _do_send(self, exchange, routing_key, msg_dict, msg_props,
+                 confirm=True, mandatory=True, persistent=False,
+                 expiration_time=None, retrier=None):
         """Send prepared message with configured retrying
 
         :param exchange: String, RabbitMQ exchange name for message sending
         :param routing_key: String, RabbitMQ routing key for message routing
         :param msg_dict: Dictionary, message payload
+        :param msg_props: Properties, message properties
         :param confirm: Boolean, enable publisher confirmation if True
         :param mandatory: Boolean, RabbitMQ publish mandatory flag (raise
             exception if it is not possible to deliver message to any queue)
@@ -474,29 +439,26 @@ class PikaOutgoingMessage(object):
         :param retrier: retrying.Retrier, configured retrier object for sending
             message, if None no retrying is performed
         """
-        properties = pika_spec.BasicProperties(
-            content_encoding=self.content_encoding,
-            content_type=self.content_type,
-            headers={_VERSION_HEADER: _VERSION},
-            delivery_mode=2 if persistent else 1
-        )
+        msg_props.delivery_mode = 2 if persistent else 1
 
         pool = (self._pika_engine.connection_with_confirmation_pool
-                if confirm else self._pika_engine.connection_pool)
+                if confirm else
+                self._pika_engine.connection_without_confirmation_pool)
 
-        body = jsonutils.dumps(msg_dict, encoding=self.content_encoding)
+        body = jsonutils.dump_as_bytes(msg_dict,
+                                       encoding=self._content_encoding)
 
         LOG.debug(
             "Sending message:[body:{}; properties: {}] to target: "
             "[exchange:{}; routing_key:{}]".format(
-                body, properties, exchange, routing_key
+                body, msg_props, exchange, routing_key
             )
         )
 
         publish = (self._publish if retrier is None else
                    retrier(self._publish))
 
-        return publish(pool, exchange, routing_key, body, properties,
+        return publish(pool, exchange, routing_key, body, msg_props,
                        mandatory, expiration_time)
 
     def send(self, exchange, routing_key='', confirm=True, mandatory=True,
@@ -515,10 +477,11 @@ class PikaOutgoingMessage(object):
         :param retrier: retrying.Retrier, configured retrier object for sending
             message, if None no retrying is performed
         """
-        msg_dict = self._prepare_message_to_send()
+        msg_dict, msg_props = self._prepare_message_to_send()
 
-        return self._do_send(exchange, routing_key, msg_dict, confirm,
-                             mandatory, persistent, expiration_time, retrier)
+        return self._do_send(exchange, routing_key, msg_dict, msg_props,
+                             confirm, mandatory, persistent, expiration_time,
+                             retrier)
 
 
 class RpcPikaOutgoingMessage(PikaOutgoingMessage):
@@ -554,23 +517,25 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
             target.topic, target.server, retrier is None
         )
 
-        msg_dict = self._prepare_message_to_send()
+        msg_dict, msg_props = self._prepare_message_to_send()
 
         if reply_listener:
-            msg_id = uuid.uuid4().hex
-            msg_dict["_msg_id"] = msg_id
-            LOG.debug('MSG_ID is %s', msg_id)
+            self.msg_id = uuid.uuid4().hex
+            msg_props.correlation_id = self.msg_id
+            LOG.debug('MSG_ID is %s', self.msg_id)
 
-            msg_dict["_reply_q"] = reply_listener.get_reply_qname(
+            self.reply_q = reply_listener.get_reply_qname(
                 expiration_time - time.time()
             )
+            msg_props.reply_to = self.reply_q
 
-            future = reply_listener.register_reply_waiter(msg_id=msg_id)
+            future = reply_listener.register_reply_waiter(msg_id=self.msg_id)
 
             self._do_send(
                 exchange=exchange, routing_key=queue, msg_dict=msg_dict,
-                confirm=True, mandatory=True, persistent=False,
-                expiration_time=expiration_time, retrier=retrier
+                msg_props=msg_props, confirm=True, mandatory=True,
+                persistent=False, expiration_time=expiration_time,
+                retrier=retrier
             )
 
             try:
@@ -580,10 +545,78 @@ class RpcPikaOutgoingMessage(PikaOutgoingMessage):
                 if isinstance(e, futures.TimeoutError):
                     e = exceptions.MessagingTimeout()
                 raise e
-
         else:
             self._do_send(
                 exchange=exchange, routing_key=queue, msg_dict=msg_dict,
-                confirm=True, mandatory=True, persistent=False,
-                expiration_time=expiration_time, retrier=retrier
+                msg_props=msg_props, confirm=True, mandatory=True,
+                persistent=False, expiration_time=expiration_time,
+                retrier=retrier
             )
+
+
+class RpcReplyPikaOutgoingMessage(PikaOutgoingMessage):
+    """PikaOutgoingMessage implementation for RPC reply messages. It sets
+    correlation_id AMQP property to link this reply with response
+    """
+    def __init__(self, pika_engine, msg_id, reply=None, failure_info=None,
+                 content_type="application/json", content_encoding="utf-8"):
+        """Initialize with reply information for sending
+
+        :param pika_engine: PikaEngine, shared object with configuration and
+            shared driver functionality
+        :param msg_id: String, msg_id of RPC request, which waits for reply
+        :param reply: Dictionary, reply. In case of exception should be None
+        :param failure_info: Tuple, should be a sys.exc_info() tuple.
+            Should be None if RPC request was successfully processed.
+        :param content_type: String, content-type header, defines serialization
+            mechanism
+        :param content_encoding: String, defines encoding for text data
+        """
+        self.msg_id = msg_id
+
+        if failure_info is not None:
+            ex_class = failure_info[0]
+            ex = failure_info[1]
+            tb = traceback.format_exception(*failure_info)
+            if issubclass(ex_class, RemoteExceptionMixin):
+                failure_data = {
+                    'c': ex.clazz,
+                    'm': ex.module,
+                    's': ex.message,
+                    't': tb
+                }
+            else:
+                failure_data = {
+                    'c': six.text_type(ex_class.__name__),
+                    'm': six.text_type(ex_class.__module__),
+                    's': six.text_type(ex),
+                    't': tb
+                }
+
+            msg = {'e': failure_data}
+        else:
+            msg = {'s': reply}
+
+        super(RpcReplyPikaOutgoingMessage, self).__init__(
+            pika_engine, msg, None, content_type, content_encoding
+        )
+
+    def send(self, reply_q, expiration_time=None, retrier=None):
+        """Send RPC message with configured retrying
+
+        :param reply_q: String, queue name for sending reply
+        :param expiration_time: Float, expiration time in seconds
+            (like time.time())
+        :param retrier: retrying.Retrier, configured retrier object for sending
+            message, if None no retrying is performed
+        """
+
+        msg_dict, msg_props = self._prepare_message_to_send()
+        msg_props.correlation_id = self.msg_id
+
+        self._do_send(
+            exchange=self._pika_engine.rpc_reply_exchange, routing_key=reply_q,
+            msg_dict=msg_dict, msg_props=msg_props, confirm=True,
+            mandatory=True, persistent=False, expiration_time=expiration_time,
+            retrier=retrier
+        )
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index 1390ced75..5aa948a2e 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -18,6 +18,7 @@ import time
 from oslo_log import log as logging
 import pika_pool
 import retrying
+import six
 
 from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
 
@@ -68,7 +69,7 @@ class PikaPoller(object):
         if self._queues_to_consume is None:
             self._queues_to_consume = self._declare_queue_binding()
 
-        for queue, no_ack in self._queues_to_consume.iteritems():
+        for queue, no_ack in six.iteritems(self._queues_to_consume):
             self._start_consuming(queue, no_ack)
 
     def _declare_queue_binding(self):
diff --git a/oslo_messaging/tests/drivers/pika/__init__.py b/oslo_messaging/tests/drivers/pika/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/oslo_messaging/tests/drivers/pika/test_message.py b/oslo_messaging/tests/drivers/pika/test_message.py
new file mode 100644
index 000000000..5008ce36e
--- /dev/null
+++ b/oslo_messaging/tests/drivers/pika/test_message.py
@@ -0,0 +1,622 @@
+#    Copyright 2015 Mirantis, 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 functools
+import time
+import unittest
+
+from concurrent import futures
+from mock import mock, patch
+from oslo_serialization import jsonutils
+import pika
+from pika import spec
+
+import oslo_messaging
+from oslo_messaging._drivers.pika_driver import pika_engine
+from oslo_messaging._drivers.pika_driver import pika_message as pika_drv_msg
+
+
+class PikaIncomingMessageTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._channel = mock.Mock()
+
+        self._delivery_tag = 12345
+
+        self._method = pika.spec.Basic.Deliver(delivery_tag=self._delivery_tag)
+        self._properties = pika.BasicProperties(
+            content_type="application/json",
+            headers={"version": "1.0"},
+        )
+        self._body = (
+            b'{"_$_key_context":"context_value",'
+            b'"payload_key": "payload_value"}'
+        )
+
+    def test_message_body_parsing(self):
+        message = pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        self.assertEqual(message.ctxt.get("key_context", None),
+                         "context_value")
+        self.assertEqual(message.message.get("payload_key", None),
+                         "payload_value")
+
+    def test_message_acknowledge(self):
+        message = pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, False
+        )
+
+        message.acknowledge()
+
+        self.assertEqual(1,  self._channel.basic_ack.call_count)
+        self.assertEqual({"delivery_tag": self._delivery_tag},
+                         self._channel.basic_ack.call_args[1])
+
+    def test_message_acknowledge_no_ack(self):
+        message = pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        message.acknowledge()
+
+        self.assertEqual(0,  self._channel.basic_ack.call_count)
+
+    def test_message_requeue(self):
+        message = pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, False
+        )
+
+        message.requeue()
+
+        self.assertEqual(1, self._channel.basic_nack.call_count)
+        self.assertEqual({"delivery_tag": self._delivery_tag, 'requeue': True},
+                         self._channel.basic_nack.call_args[1])
+
+    def test_message_requeue_no_ack(self):
+        message = pika_drv_msg.PikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        message.requeue()
+
+        self.assertEqual(0, self._channel.basic_nack.call_count)
+
+
+class RpcPikaIncomingMessageTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._pika_engine.rpc_reply_retry_attempts = 3
+        self._pika_engine.rpc_reply_retry_delay = 0.25
+
+        self._channel = mock.Mock()
+
+        self._delivery_tag = 12345
+
+        self._method = pika.spec.Basic.Deliver(delivery_tag=self._delivery_tag)
+        self._body = (
+            b'{"_$_key_context":"context_value",'
+            b'"payload_key":"payload_value"}'
+        )
+        self._properties = pika.BasicProperties(
+            content_type="application/json",
+            content_encoding="utf-8",
+            headers={"version": "1.0"},
+        )
+
+    def test_call_message_body_parsing(self):
+        self._properties.correlation_id = 123456789
+        self._properties.reply_to = "reply_queue"
+
+        message = pika_drv_msg.RpcPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        self.assertEqual(message.ctxt.get("key_context", None),
+                         "context_value")
+        self.assertEqual(message.msg_id, 123456789)
+        self.assertEqual(message.reply_q, "reply_queue")
+
+        self.assertEqual(message.message.get("payload_key", None),
+                         "payload_value")
+
+    def test_cast_message_body_parsing(self):
+        message = pika_drv_msg.RpcPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        self.assertEqual(message.ctxt.get("key_context", None),
+                         "context_value")
+        self.assertEqual(message.msg_id, None)
+        self.assertEqual(message.reply_q, None)
+
+        self.assertEqual(message.message.get("payload_key", None),
+                         "payload_value")
+
+    @patch(("oslo_messaging._drivers.pika_driver.pika_message."
+            "PikaOutgoingMessage.send"))
+    def test_reply_for_cast_message(self, send_reply_mock):
+        message = pika_drv_msg.RpcPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        self.assertEqual(message.ctxt.get("key_context", None),
+                         "context_value")
+        self.assertEqual(message.msg_id, None)
+        self.assertEqual(message.reply_q, None)
+
+        self.assertEqual(message.message.get("payload_key", None),
+                         "payload_value")
+
+        message.reply(reply=object())
+
+        self.assertEqual(send_reply_mock.call_count, 0)
+
+    @patch("oslo_messaging._drivers.pika_driver.pika_message."
+           "RpcReplyPikaOutgoingMessage")
+    @patch("retrying.retry")
+    def test_positive_reply_for_call_message(self,
+                                             retry_mock,
+                                             outgoing_message_mock):
+        self._properties.correlation_id = 123456789
+        self._properties.reply_to = "reply_queue"
+
+        message = pika_drv_msg.RpcPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        self.assertEqual(message.ctxt.get("key_context", None),
+                         "context_value")
+        self.assertEqual(message.msg_id, 123456789)
+        self.assertEqual(message.reply_q, "reply_queue")
+
+        self.assertEqual(message.message.get("payload_key", None),
+                         "payload_value")
+        reply = "all_fine"
+        message.reply(reply=reply)
+
+        outgoing_message_mock.assert_called_once_with(
+            self._pika_engine, 123456789, failure_info=None, reply='all_fine',
+            content_encoding='utf-8', content_type='application/json'
+        )
+        outgoing_message_mock().send.assert_called_once_with(
+            expiration_time=None, reply_q='reply_queue', retrier=mock.ANY
+        )
+        retry_mock.assert_called_once_with(
+            retry_on_exception=mock.ANY, stop_max_attempt_number=3,
+            wait_fixed=250.0
+        )
+
+    @patch("oslo_messaging._drivers.pika_driver.pika_message."
+           "RpcReplyPikaOutgoingMessage")
+    @patch("retrying.retry")
+    def test_negative_reply_for_call_message(self,
+                                             retry_mock,
+                                             outgoing_message_mock):
+        self._properties.correlation_id = 123456789
+        self._properties.reply_to = "reply_queue"
+
+        message = pika_drv_msg.RpcPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            self._body, True
+        )
+
+        self.assertEqual(message.ctxt.get("key_context", None),
+                         "context_value")
+        self.assertEqual(message.msg_id, 123456789)
+        self.assertEqual(message.reply_q, "reply_queue")
+
+        self.assertEqual(message.message.get("payload_key", None),
+                         "payload_value")
+
+        failure_info = object()
+        message.reply(failure=failure_info)
+
+        outgoing_message_mock.assert_called_once_with(
+            self._pika_engine, 123456789,
+            failure_info=failure_info,
+            reply=None,
+            content_encoding='utf-8',
+            content_type='application/json'
+        )
+        outgoing_message_mock().send.assert_called_once_with(
+            expiration_time=None, reply_q='reply_queue', retrier=mock.ANY
+        )
+        retry_mock.assert_called_once_with(
+            retry_on_exception=mock.ANY, stop_max_attempt_number=3,
+            wait_fixed=250.0
+        )
+
+
+class RpcReplyPikaIncomingMessageTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._pika_engine.allowed_remote_exmods = [
+            pika_engine._EXCEPTIONS_MODULE, "oslo_messaging.exceptions"
+        ]
+
+        self._channel = mock.Mock()
+
+        self._delivery_tag = 12345
+
+        self._method = pika.spec.Basic.Deliver(delivery_tag=self._delivery_tag)
+
+        self._properties = pika.BasicProperties(
+            content_type="application/json",
+            content_encoding="utf-8",
+            headers={"version": "1.0"},
+            correlation_id=123456789
+        )
+
+    def test_positive_reply_message_body_parsing(self):
+
+        body = b'{"s": "all fine"}'
+
+        message = pika_drv_msg.RpcReplyPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            body, True
+        )
+
+        self.assertEqual(message.msg_id, 123456789)
+        self.assertIsNone(message.failure)
+        self.assertEquals(message.result, "all fine")
+
+    def test_negative_reply_message_body_parsing(self):
+
+        body = (b'{'
+                b'    "e": {'
+                b'         "s": "Error message",'
+                b'         "t": ["TRACE HERE"],'
+                b'         "c": "MessagingException",'
+                b'         "m": "oslo_messaging.exceptions"'
+                b'     }'
+                b'}')
+
+        message = pika_drv_msg.RpcReplyPikaIncomingMessage(
+            self._pika_engine, self._channel, self._method, self._properties,
+            body, True
+        )
+
+        self.assertEqual(message.msg_id, 123456789)
+        self.assertIsNone(message.result)
+        self.assertEquals(
+            str(message.failure),
+            'Error message\n'
+            'TRACE HERE'
+        )
+        self.assertIsInstance(message.failure,
+                              oslo_messaging.MessagingException)
+
+
+class PikaOutgoingMessageTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.MagicMock()
+        self._exchange = "it is exchange"
+        self._routing_key = "it is routing key"
+        self._expiration = 1
+        self._expiration_time = time.time() + self._expiration
+        self._mandatory = object()
+
+        self._message = {"msg_type": 1, "msg_str": "hello"}
+        self._context = {"request_id": 555, "token": "it is a token"}
+
+    @patch("oslo_serialization.jsonutils.dumps",
+           new=functools.partial(jsonutils.dumps, sort_keys=True))
+    def test_send_with_confirmation(self):
+        message = pika_drv_msg.PikaOutgoingMessage(
+            self._pika_engine, self._message, self._context
+        )
+
+        message.send(
+            exchange=self._exchange,
+            routing_key=self._routing_key,
+            confirm=True,
+            mandatory=self._mandatory,
+            persistent=True,
+            expiration_time=self._expiration_time,
+            retrier=None
+        )
+
+        self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.assert_called_once_with(
+            body=mock.ANY,
+            exchange=self._exchange, mandatory=self._mandatory,
+            properties=mock.ANY,
+            routing_key=self._routing_key
+        )
+
+        body = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["body"]
+
+        self.assertEqual(
+            b'{"_$_request_id": 555, "_$_token": "it is a token", '
+            b'"msg_str": "hello", "msg_type": 1}',
+            body
+        )
+
+        props = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["properties"]
+
+        self.assertEqual(props.content_encoding, 'utf-8')
+        self.assertEqual(props.content_type, 'application/json')
+        self.assertEqual(props.delivery_mode, 2)
+        self.assertTrue(self._expiration * 1000 - float(props.expiration) <
+                        100)
+        self.assertEqual(props.headers, {'version': '1.0'})
+        self.assertTrue(props.message_id)
+
+    @patch("oslo_serialization.jsonutils.dumps",
+           new=functools.partial(jsonutils.dumps, sort_keys=True))
+    def test_send_without_confirmation(self):
+        message = pika_drv_msg.PikaOutgoingMessage(
+            self._pika_engine, self._message, self._context
+        )
+
+        message.send(
+            exchange=self._exchange,
+            routing_key=self._routing_key,
+            confirm=False,
+            mandatory=self._mandatory,
+            persistent=False,
+            expiration_time=self._expiration_time,
+            retrier=None
+        )
+
+        self._pika_engine.connection_without_confirmation_pool.acquire(
+        ).__enter__().channel.publish.assert_called_once_with(
+            body=mock.ANY,
+            exchange=self._exchange, mandatory=self._mandatory,
+            properties=mock.ANY,
+            routing_key=self._routing_key
+        )
+
+        body = self._pika_engine.connection_without_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["body"]
+
+        self.assertEqual(
+            b'{"_$_request_id": 555, "_$_token": "it is a token", '
+            b'"msg_str": "hello", "msg_type": 1}',
+            body
+        )
+
+        props = self._pika_engine.connection_without_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["properties"]
+
+        self.assertEqual(props.content_encoding, 'utf-8')
+        self.assertEqual(props.content_type, 'application/json')
+        self.assertEqual(props.delivery_mode, 1)
+        self.assertTrue(self._expiration * 1000 - float(props.expiration)
+                        < 100)
+        self.assertEqual(props.headers, {'version': '1.0'})
+        self.assertTrue(props.message_id)
+
+
+class RpcPikaOutgoingMessageTestCase(unittest.TestCase):
+    def setUp(self):
+        self._exchange = "it is exchange"
+        self._routing_key = "it is routing key"
+
+        self._pika_engine = mock.MagicMock()
+        self._pika_engine.get_rpc_exchange_name.return_value = self._exchange
+        self._pika_engine.get_rpc_queue_name.return_value = self._routing_key
+
+        self._message = {"msg_type": 1, "msg_str": "hello"}
+        self._context = {"request_id": 555, "token": "it is a token"}
+
+    @patch("oslo_serialization.jsonutils.dumps",
+           new=functools.partial(jsonutils.dumps, sort_keys=True))
+    def test_send_cast_message(self):
+        message = pika_drv_msg.RpcPikaOutgoingMessage(
+            self._pika_engine, self._message, self._context
+        )
+
+        expiration = 1
+        expiration_time = time.time() + expiration
+
+        message.send(
+            target=oslo_messaging.Target(exchange=self._exchange,
+                                         topic=self._routing_key),
+            reply_listener=None,
+            expiration_time=expiration_time,
+            retrier=None
+        )
+
+        self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.assert_called_once_with(
+            body=mock.ANY,
+            exchange=self._exchange, mandatory=True,
+            properties=mock.ANY,
+            routing_key=self._routing_key
+        )
+
+        body = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["body"]
+
+        self.assertEqual(
+            b'{"_$_request_id": 555, "_$_token": "it is a token", '
+            b'"msg_str": "hello", "msg_type": 1}',
+            body
+        )
+
+        props = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["properties"]
+
+        self.assertEqual(props.content_encoding, 'utf-8')
+        self.assertEqual(props.content_type, 'application/json')
+        self.assertEqual(props.delivery_mode, 1)
+        self.assertTrue(expiration * 1000 - float(props.expiration) < 100)
+        self.assertEqual(props.headers, {'version': '1.0'})
+        self.assertIsNone(props.correlation_id)
+        self.assertIsNone(props.reply_to)
+        self.assertTrue(props.message_id)
+
+    @patch("oslo_serialization.jsonutils.dumps",
+           new=functools.partial(jsonutils.dumps, sort_keys=True))
+    def test_send_call_message(self):
+        message = pika_drv_msg.RpcPikaOutgoingMessage(
+            self._pika_engine, self._message, self._context
+        )
+
+        expiration = 1
+        expiration_time = time.time() + expiration
+
+        result = "it is a result"
+        reply_queue_name = "reply_queue_name"
+
+        future = futures.Future()
+        future.set_result(result)
+        reply_listener = mock.Mock()
+        reply_listener.register_reply_waiter.return_value = future
+        reply_listener.get_reply_qname.return_value = reply_queue_name
+
+        res = message.send(
+            target=oslo_messaging.Target(exchange=self._exchange,
+                                         topic=self._routing_key),
+            reply_listener=reply_listener,
+            expiration_time=expiration_time,
+            retrier=None
+        )
+
+        self.assertEqual(result, res)
+
+        self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.assert_called_once_with(
+            body=mock.ANY,
+            exchange=self._exchange, mandatory=True,
+            properties=mock.ANY,
+            routing_key=self._routing_key
+        )
+
+        body = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["body"]
+
+        self.assertEqual(
+            b'{"_$_request_id": 555, "_$_token": "it is a token", '
+            b'"msg_str": "hello", "msg_type": 1}',
+            body
+        )
+
+        props = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["properties"]
+
+        self.assertEqual(props.content_encoding, 'utf-8')
+        self.assertEqual(props.content_type, 'application/json')
+        self.assertEqual(props.delivery_mode, 1)
+        self.assertTrue(expiration * 1000 - float(props.expiration) < 100)
+        self.assertEqual(props.headers, {'version': '1.0'})
+        self.assertEqual(props.correlation_id, message.msg_id)
+        self.assertEquals(props.reply_to, reply_queue_name)
+        self.assertTrue(props.message_id)
+
+
+class RpcReplyPikaOutgoingMessageTestCase(unittest.TestCase):
+    def setUp(self):
+        self._reply_q = "reply_queue_name"
+
+        self._expiration = 1
+        self._expiration_time = time.time() + self._expiration
+
+        self._pika_engine = mock.MagicMock()
+
+        self._rpc_reply_exchange = "rpc_reply_exchange"
+        self._pika_engine.rpc_reply_exchange = self._rpc_reply_exchange
+
+        self._msg_id = 12345567
+
+    @patch("oslo_serialization.jsonutils.dumps",
+           new=functools.partial(jsonutils.dumps, sort_keys=True))
+    def test_success_message_send(self):
+        message = pika_drv_msg.RpcReplyPikaOutgoingMessage(
+            self._pika_engine, self._msg_id, reply="all_fine"
+        )
+
+        message.send(self._reply_q, expiration_time=self._expiration_time,
+                     retrier=None)
+
+        self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.assert_called_once_with(
+            body=b'{"s": "all_fine"}',
+            exchange=self._rpc_reply_exchange, mandatory=True,
+            properties=mock.ANY,
+            routing_key=self._reply_q
+        )
+
+        props = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["properties"]
+
+        self.assertEqual(props.content_encoding, 'utf-8')
+        self.assertEqual(props.content_type, 'application/json')
+        self.assertEqual(props.delivery_mode, 1)
+        self.assertTrue(self._expiration * 1000 - float(props.expiration) <
+                        100)
+        self.assertEqual(props.headers, {'version': '1.0'})
+        self.assertEqual(props.correlation_id, message.msg_id)
+        self.assertIsNone(props.reply_to)
+        self.assertTrue(props.message_id)
+
+    @patch("traceback.format_exception", new=lambda x,y,z:z)
+    @patch("oslo_serialization.jsonutils.dumps",
+           new=functools.partial(jsonutils.dumps, sort_keys=True))
+    def test_failure_message_send(self):
+        failure_info = (oslo_messaging.MessagingException,
+                        oslo_messaging.MessagingException("Error message"),
+                        ['It is a trace'])
+
+
+        message = pika_drv_msg.RpcReplyPikaOutgoingMessage(
+            self._pika_engine, self._msg_id, failure_info=failure_info
+        )
+
+        message.send(self._reply_q, expiration_time=self._expiration_time,
+                     retrier=None)
+
+        self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.assert_called_once_with(
+            body=mock.ANY,
+            exchange=self._rpc_reply_exchange,
+            mandatory=True,
+            properties=mock.ANY,
+            routing_key=self._reply_q
+        )
+
+        body = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["body"]
+        self.assertEqual(
+            b'{"e": {"c": "MessagingException", '
+            b'"m": "oslo_messaging.exceptions", "s": "Error message", '
+            b'"t": ["It is a trace"]}}',
+            body
+        )
+
+        props = self._pika_engine.connection_with_confirmation_pool.acquire(
+        ).__enter__().channel.publish.call_args[1]["properties"]
+
+        self.assertEqual(props.content_encoding, 'utf-8')
+        self.assertEqual(props.content_type, 'application/json')
+        self.assertEqual(props.delivery_mode, 1)
+        self.assertTrue(self._expiration * 1000 - float(props.expiration) <
+                        100)
+        self.assertEqual(props.headers, {'version': '1.0'})
+        self.assertEqual(props.correlation_id, message.msg_id)
+        self.assertIsNone(props.reply_to)
+        self.assertTrue(props.message_id)

From 83a08d4b7ed36019f17a070a43ba6e4863cafe34 Mon Sep 17 00:00:00 2001
From: Dmitriy Ukhlov <dukhlov@mirantis.com>
Date: Mon, 14 Dec 2015 11:36:28 +0200
Subject: [PATCH 16/16] Adds unit tests for pika_poll module

Change-Id: I69cc0e0302382ab45ba464bb5993300d44679106
---
 .../_drivers/pika_driver/pika_engine.py       |   3 +
 .../_drivers/pika_driver/pika_message.py      |  33 +-
 .../_drivers/pika_driver/pika_poller.py       |  79 ++-
 .../tests/drivers/pika/test_message.py        |  28 +-
 .../tests/drivers/pika/test_poller.py         | 536 ++++++++++++++++++
 5 files changed, 605 insertions(+), 74 deletions(-)
 create mode 100644 oslo_messaging/tests/drivers/pika/test_poller.py

diff --git a/oslo_messaging/_drivers/pika_driver/pika_engine.py b/oslo_messaging/_drivers/pika_driver/pika_engine.py
index 6e877bb2e..4f38295a8 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_engine.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_engine.py
@@ -200,6 +200,9 @@ class PikaEngine(object):
         self._connection_host_param_list = []
         self._connection_host_status_list = []
 
+        if not url.hosts:
+            raise ValueError("You should provide at least one RabbitMQ host")
+
         for transport_host in url.hosts:
             pika_params = common_pika_params.copy()
             pika_params.update(
diff --git a/oslo_messaging/_drivers/pika_driver/pika_message.py b/oslo_messaging/_drivers/pika_driver/pika_message.py
index edd5c7328..eac2be938 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_message.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_message.py
@@ -72,18 +72,17 @@ class PikaIncomingMessage(object):
     information from RabbitMQ message and provide access to it
     """
 
-    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+    def __init__(self, pika_engine, channel, method, properties, body):
         """Parse RabbitMQ message
 
         :param pika_engine: PikaEngine, shared object with configuration and
             shared driver functionality
         :param channel: Channel, RabbitMQ channel which was used for
-            this message delivery
+            this message delivery, used for sending ack back.
+            If None - ack is not required
         :param method: Method, RabbitMQ message method
         :param properties: Properties, RabbitMQ message properties
         :param body: Bytes, RabbitMQ message body
-        :param no_ack: Boolean, defines should this message be acked by
-            consumer or not
         """
         headers = getattr(properties, "headers", {})
         version = headers.get(_VERSION_HEADER, None)
@@ -93,7 +92,6 @@ class PikaIncomingMessage(object):
                 "{}".format(version, _VERSION))
 
         self._pika_engine = pika_engine
-        self._no_ack = no_ack
         self._channel = channel
         self._delivery_tag = method.delivery_tag
 
@@ -128,12 +126,15 @@ class PikaIncomingMessage(object):
         self.message = message_dict
         self.ctxt = context_dict
 
+    def need_ack(self):
+        return self._channel is not None
+
     def acknowledge(self):
         """Ack the message. Should be called by message processing logic when
         it considered as consumed (means that we don't need redelivery of this
         message anymore)
         """
-        if not self._no_ack:
+        if self.need_ack():
             self._channel.basic_ack(delivery_tag=self._delivery_tag)
 
     def requeue(self):
@@ -141,7 +142,7 @@ class PikaIncomingMessage(object):
         when it can not process the message right now and should be redelivered
         later if it is possible
         """
-        if not self._no_ack:
+        if self.need_ack():
             return self._channel.basic_nack(delivery_tag=self._delivery_tag,
                                             requeue=True)
 
@@ -152,22 +153,21 @@ class RpcPikaIncomingMessage(PikaIncomingMessage):
     method added to allow consumer to send RPC reply back to the RPC client
     """
 
-    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+    def __init__(self, pika_engine, channel, method, properties, body):
         """Defines default values of msg_id and reply_q fields and just call
         super.__init__ method
 
         :param pika_engine: PikaEngine, shared object with configuration and
             shared driver functionality
         :param channel: Channel, RabbitMQ channel which was used for
-            this message delivery
+            this message delivery, used for sending ack back.
+            If None - ack is not required
         :param method: Method, RabbitMQ message method
         :param properties: Properties, RabbitMQ message properties
         :param body: Bytes, RabbitMQ message body
-        :param no_ack: Boolean, defines should this message be acked by
-            consumer or not
         """
         super(RpcPikaIncomingMessage, self).__init__(
-            pika_engine, channel, method, properties, body, no_ack
+            pika_engine, channel, method, properties, body
         )
         self.reply_q = properties.reply_to
         self.msg_id = properties.correlation_id
@@ -231,7 +231,7 @@ class RpcReplyPikaIncomingMessage(PikaIncomingMessage):
     """PikaIncomingMessage implementation for RPC reply messages. It expects
     extra RPC reply related fields in message body (result and failure).
     """
-    def __init__(self, pika_engine, channel, method, properties, body, no_ack):
+    def __init__(self, pika_engine, channel, method, properties, body):
         """Defines default values of result and failure fields, call
         super.__init__ method and then construct Exception object if failure is
         not None
@@ -239,15 +239,14 @@ class RpcReplyPikaIncomingMessage(PikaIncomingMessage):
         :param pika_engine: PikaEngine, shared object with configuration and
             shared driver functionality
         :param channel: Channel, RabbitMQ channel which was used for
-            this message delivery
+            this message delivery, used for sending ack back.
+            If None - ack is not required
         :param method: Method, RabbitMQ message method
         :param properties: Properties, RabbitMQ message properties
         :param body: Bytes, RabbitMQ message body
-        :param no_ack: Boolean, defines should this message be acked by
-            consumer or not
         """
         super(RpcReplyPikaIncomingMessage, self).__init__(
-            pika_engine, channel, method, properties, body, no_ack
+            pika_engine, channel, method, properties, body
         )
 
         self.msg_id = properties.correlation_id
diff --git a/oslo_messaging/_drivers/pika_driver/pika_poller.py b/oslo_messaging/_drivers/pika_driver/pika_poller.py
index 5aa948a2e..3533dad2f 100644
--- a/oslo_messaging/_drivers/pika_driver/pika_poller.py
+++ b/oslo_messaging/_drivers/pika_driver/pika_poller.py
@@ -31,8 +31,7 @@ class PikaPoller(object):
     connectivity related problem detected
     """
 
-    def __init__(self, pika_engine, prefetch_count,
-                 incoming_message_class=pika_drv_msg.PikaIncomingMessage):
+    def __init__(self, pika_engine, prefetch_count, incoming_message_class):
         """Initialize required fields
 
         :param pika_engine: PikaEngine, shared object with configuration and
@@ -110,8 +109,7 @@ class PikaPoller(object):
         """
         self._message_queue.append(
             self._incoming_message_class(
-                self._pika_engine, self._channel, method, properties, body,
-                True
+                self._pika_engine, None, method, properties, body
             )
         )
 
@@ -121,8 +119,7 @@ class PikaPoller(object):
         """
         self._message_queue.append(
             self._incoming_message_class(
-                self._pika_engine, self._channel, method, properties, body,
-                False
+                self._pika_engine, self._channel, method, properties, body
             )
         )
 
@@ -146,6 +143,11 @@ class PikaPoller(object):
                     LOG.exception("Unexpected error during closing connection")
             self._connection = None
 
+        for i in xrange(len(self._message_queue) - 1, -1, -1):
+            message = self._message_queue[i]
+            if message.need_ack():
+                del self._message_queue[i]
+
     def poll(self, timeout=None, prefetch_size=1):
         """Main method of this class - consumes message from RabbitMQ
 
@@ -158,32 +160,29 @@ class PikaPoller(object):
         """
         expiration_time = time.time() + timeout if timeout else None
 
-        while len(self._message_queue) < prefetch_size:
+        while True:
             with self._lock:
-                if not self._started:
-                    return None
-
-                try:
-                    if self._channel is None:
-                        self._reconnect()
-                    # we need some time_limit here, not too small to avoid a
-                    # lot of not needed iterations but not too large to release
-                    # lock time to time and give a chance to perform another
-                    # method waiting this lock
-                    self._connection.process_data_events(
-                        time_limit=0.25
-                    )
-                except Exception as e:
-                    LOG.warn("Exception during consuming message. " + str(e))
-                    self._cleanup()
-            if timeout is not None:
-                timeout = expiration_time - time.time()
-                if timeout <= 0:
-                    break
-
-        result = self._message_queue[:prefetch_size]
-        self._message_queue = self._message_queue[prefetch_size:]
-        return result
+                if timeout is not None:
+                    timeout = expiration_time - time.time()
+                if (len(self._message_queue) < prefetch_size and
+                        self._started and ((timeout is None) or timeout > 0)):
+                    try:
+                        if self._channel is None:
+                            self._reconnect()
+                        # we need some time_limit here, not too small to avoid
+                        # a lot of not needed iterations but not too large to
+                        # release lock time to time and give a chance to
+                        # perform another method waiting this lock
+                        self._connection.process_data_events(
+                            time_limit=0.25
+                        )
+                    except pika_pool.Connection.connectivity_errors:
+                        self._cleanup()
+                        raise
+                else:
+                    result = self._message_queue[:prefetch_size]
+                    del self._message_queue[:prefetch_size]
+                    return result
 
     def start(self):
         """Starts poller. Should be called before polling to allow message
@@ -201,7 +200,6 @@ class PikaPoller(object):
                 return
 
             self._started = False
-            self._cleanup()
 
     def reconnect(self):
         """Safe version of _reconnect. Performs reconnection to the broker."""
@@ -249,9 +247,7 @@ class RpcServicePikaPoller(PikaPoller):
 
         :return Dictionary, declared_queue_name -> no_ack_mode
         """
-        queue_expiration = (
-            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
-        )
+        queue_expiration = self._pika_engine.rpc_queue_expiration
 
         queues_to_consume = {}
 
@@ -319,15 +315,11 @@ class RpcReplyPikaPoller(PikaPoller):
 
         :return Dictionary, declared_queue_name -> no_ack_mode
         """
-        queue_expiration = (
-            self._pika_engine.conf.oslo_messaging_pika.rpc_queue_expiration
-        )
-
         self._pika_engine.declare_queue_binding_by_channel(
             channel=self._channel,
             exchange=self._exchange, queue=self._queue,
             routing_key=self._queue, exchange_type='direct',
-            queue_expiration=queue_expiration,
+            queue_expiration=self._pika_engine.rpc_queue_expiration,
             durable=False
         )
 
@@ -363,8 +355,8 @@ class NotificationPikaPoller(PikaPoller):
     """
     def __init__(self, pika_engine, targets_and_priorities,
                  queue_name=None, prefetch_count=100):
-        """Adds exchange and queue parameter for declaring exchange and queue
-        used for RPC reply delivery
+        """Adds targets_and_priorities and queue_name parameter
+        for declaring exchanges and queues used for notification delivery
 
         :param pika_engine: PikaEngine, shared object with configuration and
             shared driver functionality
@@ -379,7 +371,8 @@ class NotificationPikaPoller(PikaPoller):
         self._queue_name = queue_name
 
         super(NotificationPikaPoller, self).__init__(
-            pika_engine, prefetch_count=prefetch_count
+            pika_engine, prefetch_count=prefetch_count,
+            incoming_message_class=pika_drv_msg.PikaIncomingMessage
         )
 
     def _declare_queue_binding(self):
diff --git a/oslo_messaging/tests/drivers/pika/test_message.py b/oslo_messaging/tests/drivers/pika/test_message.py
index 5008ce36e..3c3f87e39 100644
--- a/oslo_messaging/tests/drivers/pika/test_message.py
+++ b/oslo_messaging/tests/drivers/pika/test_message.py
@@ -46,7 +46,7 @@ class PikaIncomingMessageTestCase(unittest.TestCase):
     def test_message_body_parsing(self):
         message = pika_drv_msg.PikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._body
         )
 
         self.assertEqual(message.ctxt.get("key_context", None),
@@ -57,7 +57,7 @@ class PikaIncomingMessageTestCase(unittest.TestCase):
     def test_message_acknowledge(self):
         message = pika_drv_msg.PikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, False
+            self._body
         )
 
         message.acknowledge()
@@ -68,8 +68,8 @@ class PikaIncomingMessageTestCase(unittest.TestCase):
 
     def test_message_acknowledge_no_ack(self):
         message = pika_drv_msg.PikaIncomingMessage(
-            self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._pika_engine, None, self._method, self._properties,
+            self._body
         )
 
         message.acknowledge()
@@ -79,7 +79,7 @@ class PikaIncomingMessageTestCase(unittest.TestCase):
     def test_message_requeue(self):
         message = pika_drv_msg.PikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, False
+            self._body
         )
 
         message.requeue()
@@ -90,8 +90,8 @@ class PikaIncomingMessageTestCase(unittest.TestCase):
 
     def test_message_requeue_no_ack(self):
         message = pika_drv_msg.PikaIncomingMessage(
-            self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._pika_engine, None, self._method, self._properties,
+            self._body
         )
 
         message.requeue()
@@ -126,7 +126,7 @@ class RpcPikaIncomingMessageTestCase(unittest.TestCase):
 
         message = pika_drv_msg.RpcPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._body
         )
 
         self.assertEqual(message.ctxt.get("key_context", None),
@@ -140,7 +140,7 @@ class RpcPikaIncomingMessageTestCase(unittest.TestCase):
     def test_cast_message_body_parsing(self):
         message = pika_drv_msg.RpcPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._body
         )
 
         self.assertEqual(message.ctxt.get("key_context", None),
@@ -156,7 +156,7 @@ class RpcPikaIncomingMessageTestCase(unittest.TestCase):
     def test_reply_for_cast_message(self, send_reply_mock):
         message = pika_drv_msg.RpcPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._body
         )
 
         self.assertEqual(message.ctxt.get("key_context", None),
@@ -182,7 +182,7 @@ class RpcPikaIncomingMessageTestCase(unittest.TestCase):
 
         message = pika_drv_msg.RpcPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._body
         )
 
         self.assertEqual(message.ctxt.get("key_context", None),
@@ -218,7 +218,7 @@ class RpcPikaIncomingMessageTestCase(unittest.TestCase):
 
         message = pika_drv_msg.RpcPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            self._body, True
+            self._body
         )
 
         self.assertEqual(message.ctxt.get("key_context", None),
@@ -274,7 +274,7 @@ class RpcReplyPikaIncomingMessageTestCase(unittest.TestCase):
 
         message = pika_drv_msg.RpcReplyPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            body, True
+            body
         )
 
         self.assertEqual(message.msg_id, 123456789)
@@ -294,7 +294,7 @@ class RpcReplyPikaIncomingMessageTestCase(unittest.TestCase):
 
         message = pika_drv_msg.RpcReplyPikaIncomingMessage(
             self._pika_engine, self._channel, self._method, self._properties,
-            body, True
+            body
         )
 
         self.assertEqual(message.msg_id, 123456789)
diff --git a/oslo_messaging/tests/drivers/pika/test_poller.py b/oslo_messaging/tests/drivers/pika/test_poller.py
new file mode 100644
index 000000000..77a3b6b29
--- /dev/null
+++ b/oslo_messaging/tests/drivers/pika/test_poller.py
@@ -0,0 +1,536 @@
+#    Copyright 2015 Mirantis, 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 time
+import unittest
+
+import mock
+
+from oslo_messaging._drivers.pika_driver import pika_poller
+
+
+class PikaPollerTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._poller_connection_mock = mock.Mock()
+        self._poller_channel_mock = mock.Mock()
+        self._poller_connection_mock.channel.return_value = (
+            self._poller_channel_mock
+        )
+        self._pika_engine.create_connection.return_value = (
+            self._poller_connection_mock
+        )
+        self._prefetch_count = 123
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_poller.PikaPoller."
+                "_declare_queue_binding")
+    def test_poll(self, declare_queue_binding_mock):
+        incoming_message_class_mock = mock.Mock()
+        poller = pika_poller.PikaPoller(
+            self._pika_engine, self._prefetch_count,
+            incoming_message_class=incoming_message_class_mock
+        )
+        unused = object()
+        method = object()
+        properties = object()
+        body = object()
+
+        self._poller_connection_mock.process_data_events.side_effect = (
+            lambda time_limit: poller._on_message_with_ack_callback(
+                unused, method, properties, body
+            )
+        )
+
+        poller.start()
+        res = poller.poll()
+
+        self.assertEqual(len(res), 1)
+
+        self.assertEqual(res[0], incoming_message_class_mock.return_value)
+        incoming_message_class_mock.assert_called_once_with(
+            self._pika_engine, self._poller_channel_mock, method, properties,
+            body
+        )
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        self.assertTrue(declare_queue_binding_mock.called)
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_poller.PikaPoller."
+                "_declare_queue_binding")
+    def test_poll_after_stop(self, declare_queue_binding_mock):
+        incoming_message_class_mock = mock.Mock()
+        poller = pika_poller.PikaPoller(
+            self._pika_engine, self._prefetch_count,
+            incoming_message_class=incoming_message_class_mock
+        )
+
+        n = 10
+        params = []
+
+        for i in range(n):
+            params.append((object(), object(), object(), object()))
+
+        index = [0]
+
+        def f(time_limit):
+            for i in range(10):
+                poller._on_message_no_ack_callback(
+                    *params[index[0]]
+                )
+                index[0] += 1
+
+        self._poller_connection_mock.process_data_events.side_effect = f
+
+        poller.start()
+        res = poller.poll(prefetch_size=1)
+        self.assertEqual(len(res), 1)
+        self.assertEqual(res[0], incoming_message_class_mock.return_value)
+        self.assertEqual(
+            incoming_message_class_mock.call_args_list[0][0],
+            (self._pika_engine, None) + params[0][1:]
+        )
+
+        poller.stop()
+
+        res2 = poller.poll(prefetch_size=n)
+
+        self.assertEqual(len(res2), n-1)
+        self.assertEqual(incoming_message_class_mock.call_count, n)
+
+        self.assertEqual(
+            self._poller_connection_mock.process_data_events.call_count, 1)
+
+        for i in range(n-1):
+            self.assertEqual(res2[i], incoming_message_class_mock.return_value)
+            self.assertEqual(
+                incoming_message_class_mock.call_args_list[i+1][0],
+                (self._pika_engine, None) + params[i+1][1:]
+            )
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        self.assertTrue(declare_queue_binding_mock.called)
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_poller.PikaPoller."
+                "_declare_queue_binding")
+    def test_poll_batch(self, declare_queue_binding_mock):
+        incoming_message_class_mock = mock.Mock()
+        poller = pika_poller.PikaPoller(
+            self._pika_engine, self._prefetch_count,
+            incoming_message_class=incoming_message_class_mock
+        )
+
+        n = 10
+        params = []
+
+        for i in range(n):
+            params.append((object(), object(), object(), object()))
+
+        index = [0]
+
+        def f(time_limit):
+            poller._on_message_with_ack_callback(
+                *params[index[0]]
+            )
+            index[0] += 1
+
+        self._poller_connection_mock.process_data_events.side_effect = f
+
+        poller.start()
+        res = poller.poll(prefetch_size=n)
+
+        self.assertEqual(len(res), n)
+        self.assertEqual(incoming_message_class_mock.call_count, n)
+
+        for i in range(n):
+            self.assertEqual(res[i], incoming_message_class_mock.return_value)
+            self.assertEqual(
+                incoming_message_class_mock.call_args_list[i][0],
+                (self._pika_engine, self._poller_channel_mock) + params[i][1:]
+            )
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        self.assertTrue(declare_queue_binding_mock.called)
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_poller.PikaPoller."
+                "_declare_queue_binding")
+    def test_poll_batch_with_timeout(self, declare_queue_binding_mock):
+        incoming_message_class_mock = mock.Mock()
+        poller = pika_poller.PikaPoller(
+            self._pika_engine, self._prefetch_count,
+            incoming_message_class=incoming_message_class_mock
+        )
+
+        n = 10
+        timeout = 1
+        sleep_time = 0.2
+        params = []
+
+        success_count = 5
+
+        for i in range(n):
+            params.append((object(), object(), object(), object()))
+
+        index = [0]
+
+        def f(time_limit):
+            time.sleep(sleep_time)
+            poller._on_message_with_ack_callback(
+                *params[index[0]]
+            )
+            index[0] += 1
+
+        self._poller_connection_mock.process_data_events.side_effect = f
+
+        poller.start()
+        res = poller.poll(prefetch_size=n, timeout=timeout)
+
+        self.assertEqual(len(res), success_count)
+        self.assertEqual(incoming_message_class_mock.call_count, success_count)
+
+        for i in range(success_count):
+            self.assertEqual(res[i], incoming_message_class_mock.return_value)
+            self.assertEqual(
+                incoming_message_class_mock.call_args_list[i][0],
+                (self._pika_engine, self._poller_channel_mock) + params[i][1:]
+            )
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        self.assertTrue(declare_queue_binding_mock.called)
+
+
+class RpcServicePikaPollerTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._poller_connection_mock = mock.Mock()
+        self._poller_channel_mock = mock.Mock()
+        self._poller_connection_mock.channel.return_value = (
+            self._poller_channel_mock
+        )
+        self._pika_engine.create_connection.return_value = (
+            self._poller_connection_mock
+        )
+
+        self._pika_engine.get_rpc_queue_name.side_effect = (
+            lambda topic, server, no_ack: "_".join(
+                [topic, str(server), str(no_ack)]
+            )
+        )
+
+        self._pika_engine.get_rpc_exchange_name.side_effect = (
+            lambda exchange, topic, fanout, no_ack: "_".join(
+                [exchange, topic, str(fanout), str(no_ack)]
+            )
+        )
+
+        self._prefetch_count = 123
+        self._target = mock.Mock(exchange="exchange", topic="topic",
+                                 server="server")
+        self._pika_engine.rpc_queue_expiration = 12345
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_message."
+                "RpcPikaIncomingMessage")
+    def test_declare_rpc_queue_bindings(self, rpc_pika_incoming_message_mock):
+        poller = pika_poller.RpcServicePikaPoller(
+            self._pika_engine, self._target, self._prefetch_count,
+        )
+        self._poller_connection_mock.process_data_events.side_effect = (
+            lambda time_limit: poller._on_message_with_ack_callback(
+                None, None, None, None
+            )
+        )
+
+        poller.start()
+        res = poller.poll()
+
+        self.assertEqual(len(res), 1)
+
+        self.assertEqual(res[0], rpc_pika_incoming_message_mock.return_value)
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        declare_queue_binding_by_channel_mock = (
+            self._pika_engine.declare_queue_binding_by_channel
+        )
+
+        self.assertEqual(
+            declare_queue_binding_by_channel_mock.call_count, 6
+        )
+
+        declare_queue_binding_by_channel_mock.assert_has_calls((
+            mock.call(
+                channel=self._poller_channel_mock, durable=False,
+                exchange="exchange_topic_False_True",
+                exchange_type='direct',
+                queue="topic_None_True",
+                queue_expiration=12345,
+                routing_key="topic_None_True"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock, durable=False,
+                exchange="exchange_topic_False_True",
+                exchange_type='direct',
+                queue="topic_server_True",
+                queue_expiration=12345,
+                routing_key="topic_server_True"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock, durable=False,
+                exchange="exchange_topic_True_True",
+                exchange_type='fanout',
+                queue="topic_server_True",
+                queue_expiration=12345,
+                routing_key=''
+            ),
+            mock.call(
+                channel=self._poller_channel_mock, durable=False,
+                exchange="exchange_topic_False_False",
+                exchange_type='direct',
+                queue="topic_None_False",
+                queue_expiration=12345,
+                routing_key="topic_None_False"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock, durable=False,
+                exchange="exchange_topic_False_False",
+                exchange_type='direct',
+                queue="topic_server_False",
+                queue_expiration=12345,
+                routing_key="topic_server_False"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock, durable=False,
+                exchange="exchange_topic_True_False",
+                exchange_type='fanout',
+                queue="topic_server_False",
+                queue_expiration=12345,
+                routing_key=''
+            ),
+        ))
+
+
+class RpcReplyServicePikaPollerTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._poller_connection_mock = mock.Mock()
+        self._poller_channel_mock = mock.Mock()
+        self._poller_connection_mock.channel.return_value = (
+            self._poller_channel_mock
+        )
+        self._pika_engine.create_connection.return_value = (
+            self._poller_connection_mock
+        )
+
+        self._prefetch_count = 123
+        self._exchange = "rpc_reply_exchange"
+        self._queue = "rpc_reply_queue"
+
+        self._pika_engine.rpc_reply_retry_delay = 12132543456
+
+        self._pika_engine.rpc_queue_expiration = 12345
+        self._pika_engine.rpc_reply_retry_attempts = 3
+
+    def test_start(self):
+        poller = pika_poller.RpcReplyPikaPoller(
+            self._pika_engine, self._exchange, self._queue,
+            self._prefetch_count,
+        )
+
+        poller.start()
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+    def test_declare_rpc_reply_queue_binding(self):
+        poller = pika_poller.RpcReplyPikaPoller(
+            self._pika_engine, self._exchange, self._queue,
+            self._prefetch_count,
+        )
+
+        poller.start()
+
+        declare_queue_binding_by_channel_mock = (
+            self._pika_engine.declare_queue_binding_by_channel
+        )
+
+        self.assertEqual(
+            declare_queue_binding_by_channel_mock.call_count, 1
+        )
+
+        declare_queue_binding_by_channel_mock.assert_called_once_with(
+            channel=self._poller_channel_mock, durable=False,
+            exchange='rpc_reply_exchange', exchange_type='direct',
+            queue='rpc_reply_queue', queue_expiration=12345,
+            routing_key='rpc_reply_queue'
+        )
+
+
+class NotificationPikaPollerTestCase(unittest.TestCase):
+    def setUp(self):
+        self._pika_engine = mock.Mock()
+        self._poller_connection_mock = mock.Mock()
+        self._poller_channel_mock = mock.Mock()
+        self._poller_connection_mock.channel.return_value = (
+            self._poller_channel_mock
+        )
+        self._pika_engine.create_connection.return_value = (
+            self._poller_connection_mock
+        )
+
+        self._prefetch_count = 123
+        self._target_and_priorities = (
+            (
+                mock.Mock(exchange="exchange1", topic="topic1",
+                          server="server1"), 1
+            ),
+            (
+                mock.Mock(exchange="exchange1", topic="topic1"), 2
+            ),
+            (
+                mock.Mock(exchange="exchange2", topic="topic2",), 1
+            ),
+        )
+        self._pika_engine.notification_persistence = object()
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_message."
+                "PikaIncomingMessage")
+    def test_declare_notification_queue_bindings_default_queue(
+            self, pika_incoming_message_mock):
+        poller = pika_poller.NotificationPikaPoller(
+            self._pika_engine, self._target_and_priorities, None,
+            self._prefetch_count,
+        )
+        self._poller_connection_mock.process_data_events.side_effect = (
+            lambda time_limit: poller._on_message_with_ack_callback(
+                None, None, None, None
+            )
+        )
+
+        poller.start()
+        res = poller.poll()
+
+        self.assertEqual(len(res), 1)
+
+        self.assertEqual(res[0], pika_incoming_message_mock.return_value)
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        declare_queue_binding_by_channel_mock = (
+            self._pika_engine.declare_queue_binding_by_channel
+        )
+
+        self.assertEqual(
+            declare_queue_binding_by_channel_mock.call_count, 3
+        )
+
+        declare_queue_binding_by_channel_mock.assert_has_calls((
+            mock.call(
+                channel=self._poller_channel_mock,
+                durable=self._pika_engine.notification_persistence,
+                exchange="exchange1",
+                exchange_type='direct',
+                queue="topic1.1",
+                queue_expiration=None,
+                routing_key="topic1.1"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock,
+                durable=self._pika_engine.notification_persistence,
+                exchange="exchange1",
+                exchange_type='direct',
+                queue="topic1.2",
+                queue_expiration=None,
+                routing_key="topic1.2"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock,
+                durable=self._pika_engine.notification_persistence,
+                exchange="exchange2",
+                exchange_type='direct',
+                queue="topic2.1",
+                queue_expiration=None,
+                routing_key="topic2.1"
+            )
+        ))
+
+    @mock.patch("oslo_messaging._drivers.pika_driver.pika_message."
+                "PikaIncomingMessage")
+    def test_declare_notification_queue_bindings_custom_queue(
+            self, pika_incoming_message_mock):
+        poller = pika_poller.NotificationPikaPoller(
+            self._pika_engine, self._target_and_priorities,
+            "custom_queue_name", self._prefetch_count
+        )
+        self._poller_connection_mock.process_data_events.side_effect = (
+            lambda time_limit: poller._on_message_with_ack_callback(
+                None, None, None, None
+            )
+        )
+
+        poller.start()
+        res = poller.poll()
+
+        self.assertEqual(len(res), 1)
+
+        self.assertEqual(res[0], pika_incoming_message_mock.return_value)
+
+        self.assertTrue(self._pika_engine.create_connection.called)
+        self.assertTrue(self._poller_connection_mock.channel.called)
+
+        declare_queue_binding_by_channel_mock = (
+            self._pika_engine.declare_queue_binding_by_channel
+        )
+
+        self.assertEqual(
+            declare_queue_binding_by_channel_mock.call_count, 3
+        )
+
+        declare_queue_binding_by_channel_mock.assert_has_calls((
+            mock.call(
+                channel=self._poller_channel_mock,
+                durable=self._pika_engine.notification_persistence,
+                exchange="exchange1",
+                exchange_type='direct',
+                queue="custom_queue_name",
+                queue_expiration=None,
+                routing_key="topic1.1"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock,
+                durable=self._pika_engine.notification_persistence,
+                exchange="exchange1",
+                exchange_type='direct',
+                queue="custom_queue_name",
+                queue_expiration=None,
+                routing_key="topic1.2"
+            ),
+            mock.call(
+                channel=self._poller_channel_mock,
+                durable=self._pika_engine.notification_persistence,
+                exchange="exchange2",
+                exchange_type='direct',
+                queue="custom_queue_name",
+                queue_expiration=None,
+                routing_key="topic2.1"
+            )
+        ))