154 lines
4.9 KiB
Python
154 lines
4.9 KiB
Python
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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 threading import Timer
|
|
|
|
import pika
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
|
|
from storyboard._i18n import _, _LI
|
|
|
|
|
|
CONF = cfg.CONF
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class ConnectionService(object):
|
|
"""A generic amqp connection agent that handles unexpected
|
|
interactions with RabbitMQ such as channel and connection closures,
|
|
by reconnecting on failure.
|
|
"""
|
|
|
|
def __init__(self, conf):
|
|
"""Setup the connection instance based on our configuration.
|
|
|
|
:param conf A configuration object.
|
|
"""
|
|
self._connection = None
|
|
self._channel = None
|
|
self._open = False
|
|
self.started = False
|
|
self._timer = None
|
|
self._closing = False
|
|
self._open_hooks = set()
|
|
self._exchange_name = conf.rabbit_exchange_name
|
|
self._application_id = conf.rabbit_application_name
|
|
self._properties = pika.BasicProperties(
|
|
app_id='storyboard', content_type='application/json')
|
|
self._connection_credentials = pika.PlainCredentials(
|
|
conf.rabbit_userid,
|
|
conf.rabbit_password)
|
|
self._connection_parameters = pika.ConnectionParameters(
|
|
conf.rabbit_host,
|
|
conf.rabbit_port,
|
|
conf.rabbit_virtual_host,
|
|
self._connection_credentials)
|
|
|
|
def _connect(self):
|
|
"""This method connects to RabbitMQ, establishes a channel, declares
|
|
the storyboard exchange if it doesn't yet exist, and executes any
|
|
post-connection hooks that an extending class may have registered.
|
|
"""
|
|
|
|
# If the closing flag is set, just exit.
|
|
if self._closing:
|
|
return
|
|
|
|
# If a timer is set, kill it.
|
|
if self._timer:
|
|
LOG.debug(_('Clearing timer...'))
|
|
self._timer.cancel()
|
|
self._timer = None
|
|
|
|
# Create the connection
|
|
LOG.info(_LI('Connecting to %s'), self._connection_parameters.host)
|
|
self._connection = pika.BlockingConnection(self._connection_parameters)
|
|
|
|
# Create a channel
|
|
LOG.debug(_('Creating a new channel'))
|
|
self._channel = self._connection.channel()
|
|
self._channel.confirm_delivery()
|
|
|
|
# Declare the exchange
|
|
LOG.debug(_('Declaring exchange %s'), self._exchange_name)
|
|
self._channel.exchange_declare(exchange=self._exchange_name,
|
|
exchange_type='topic',
|
|
durable=True,
|
|
auto_delete=False)
|
|
|
|
# Set the open flag and execute any connection hooks.
|
|
self._open = True
|
|
self._execute_open_hooks()
|
|
|
|
def _reconnect(self):
|
|
"""Reconnect to rabbit.
|
|
"""
|
|
|
|
# Sanity check - if we're closing, do nothing.
|
|
if self._closing:
|
|
return
|
|
|
|
# If a timer is already there, assume it's doing its thing...
|
|
if self._timer:
|
|
return
|
|
LOG.debug(_('Scheduling reconnect in 5 seconds...'))
|
|
self._timer = Timer(5, self._connect)
|
|
self._timer.start()
|
|
|
|
def _close(self):
|
|
"""This method closes the connection to RabbitMQ."""
|
|
LOG.info(_LI('Closing connection'))
|
|
self._open = False
|
|
if self._channel:
|
|
self._channel.close()
|
|
self._channel = None
|
|
if self._connection:
|
|
self._connection.close()
|
|
self._connection = None
|
|
self._closing = False
|
|
LOG.debug(_('Connection Closed'))
|
|
|
|
def _execute_open_hooks(self):
|
|
"""Executes all hooks that have been registered to run on open.
|
|
"""
|
|
for hook in self._open_hooks:
|
|
hook()
|
|
|
|
def start(self):
|
|
"""Start the publisher, opening a connection to RabbitMQ. This method
|
|
must be explicitly invoked, otherwise any messages will simply be
|
|
cached for later broadcast.
|
|
"""
|
|
|
|
# Create the connection.
|
|
self.started = True
|
|
self._closing = False
|
|
self._connect()
|
|
|
|
def stop(self):
|
|
"""Stop the publisher by closing the channel and the connection.
|
|
"""
|
|
self.started = False
|
|
self._closing = True
|
|
self._close()
|
|
|
|
def add_open_hook(self, hook):
|
|
"""Add a method that will be executed whenever a connection is
|
|
established.
|
|
"""
|
|
self._open_hooks.add(hook)
|