Add support for multiple AMQP rpc backends

This is based on Oslo messaging API that supports RPC and notifications over a
number of different messsaging transports.

* remove old powervc.common.messaging and create a new one to adapt to Oslo
messaging

* adapt all sync service managers to new messaging model

Change-Id: I0c9b4a9fa5bb5d0eaac1433e768a110871d8dab8
Closes-Bug: 1363618
This commit is contained in:
Le Tian Ren 2014-09-12 19:46:57 +08:00
parent 05a8443e12
commit 0eefba3f77
12 changed files with 678 additions and 1144 deletions

View File

@ -14,11 +14,16 @@ from cinder.openstack.common import log
from powervc.common import config
from powervc.common.gettextutils import _
from powervc.volume.manager import constants
from powervc.common import messaging
from powervc.volume.driver import service as pvcservice
from powervc.common import utils
from powervc.common.client import delegate as ctx_delegate
from powervc.common import messaging
from oslo.messaging.notify import listener
from oslo.messaging import target
from oslo.messaging import transport
CONF = config.CONF
LOG = log.getLogger(__name__)
@ -142,82 +147,82 @@ class PowerVCCinderManager(service.Service):
self.volume_service.start()
def _create_powervc_listeners(self, ctx):
"""
Listen for out-of-band changes made in PowerVC.
"""Listen for out-of-band changes made in PowerVC.
This method creates the connection to the PowerVC Qpid broker and
This method creates the listner to the PowerVC AMQP broker and
sets up handlers so that any changes made directly in PowerVC are
reflected in the local OS.
:param: ctx The security context
"""
LOG.debug("Enter _create_powervc_listeners method")
# Function to call if we lose the Qpid connection and then get it back
def reconnect_handler():
LOG.debug('Re-established connection to Qpid broker, sync all '
'volume types on next sync interval')
self.full_volume_type_sync_required = True
# Create Qpid connection and listener
LOG.debug("Building connection with AMQP server")
conn = messaging.PowerVCConnection(reconnect_handler=reconnect_handler,
context=ctx,
log=logging)
LOG.debug("Creating message listener to linsten PowerVC event")
listener = conn.create_listener('cinder', 'notifications.info')
trans = transport.get_transport(config.AMQP_POWERVC_CONF)
targets = [
target.Target(exchange='cinder', topic='notifications')
]
endpoint = messaging.NotificationEndpoint(log=LOG, sec_context=ctx)
# Volume type creation
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_TYPE_CREATE)
listener.register_handler(constants.EVENT_VOLUME_TYPE_CREATE,
endpoint.register_handler(constants.EVENT_VOLUME_TYPE_CREATE,
self._handle_powervc_volume_type_create)
# Volume type deletion
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_TYPE_DELETE)
listener.register_handler(constants.EVENT_VOLUME_TYPE_DELETE,
endpoint.register_handler(constants.EVENT_VOLUME_TYPE_DELETE,
self._handle_powervc_volume_type_delete)
# Volume type extra spec changes
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_TYPE_EXTRA_SPECS_UPDATE)
listener.register_handler([
endpoint.register_handler([
constants.EVENT_VOLUME_TYPE_EXTRA_SPECS_UPDATE],
self._handle_powervc_volume_type_extra_spec_update)
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_CREATE_END)
listener.register_handler([constants.EVENT_VOLUME_CREATE_END],
endpoint.register_handler([constants.EVENT_VOLUME_CREATE_END],
self._handle_powervc_volume_create)
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_IMPORT_END)
listener.register_handler([constants.EVENT_VOLUME_IMPORT_END],
endpoint.register_handler([constants.EVENT_VOLUME_IMPORT_END],
self._handle_powervc_volume_create)
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_DELETE_END)
listener.register_handler([constants.EVENT_VOLUME_DELETE_END],
endpoint.register_handler([constants.EVENT_VOLUME_DELETE_END],
self._handle_powervc_volume_delete)
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_UPDATE)
listener.register_handler([constants.EVENT_VOLUME_UPDATE],
endpoint.register_handler([constants.EVENT_VOLUME_UPDATE],
self._handle_powervc_volume_update)
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_ATTACH_END)
listener.register_handler([constants.EVENT_VOLUME_ATTACH_END],
endpoint.register_handler([constants.EVENT_VOLUME_ATTACH_END],
self._handle_powervc_volume_update)
LOG.debug(_("Register event handler for %s event ")
% constants.EVENT_VOLUME_DETACH_END)
listener.register_handler([constants.EVENT_VOLUME_DETACH_END],
endpoint.register_handler([constants.EVENT_VOLUME_DETACH_END],
self._handle_powervc_volume_update)
endpoints = [
endpoint,
]
LOG.debug("Starting to listen...... ")
conn.start()
pvc_cinder_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(pvc_cinder_listener)
LOG.debug("Exit _create_powervc_listeners method")
def _periodic_volume_type_sync(self, context, vol_type_ids=None):
@ -373,16 +378,19 @@ class PowerVCCinderManager(service.Service):
self.tg.add_timer(sync_interval, sync)
def _handle_powervc_volume_type_create(self, context, message):
"""
Handle instance create messages sent from PowerVC.
def _handle_powervc_volume_type_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle instance create messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
LOG.debug("Handling notification: %s" % message.get('event_type'))
payload = message.get('payload')
vol_type = payload.get('volume_types')
if(vol_type is None):
LOG.warning("Null volume type in volume.create notification")
@ -430,16 +438,19 @@ class PowerVCCinderManager(service.Service):
if volume_backend_name == storage_hostname:
self._insert_pvc_volume_type(context, vol_type)
def _handle_powervc_volume_type_delete(self, context, message):
"""
Handle instance delete messages sent from PowerVC.
def _handle_powervc_volume_type_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle instance delete messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
LOG.debug("Handling notification: %s" % message.get('event_type'))
payload = message.get('payload')
vol_type = payload.get('volume_types')
if(vol_type is None):
LOG.warning("Null volume type, ignore volume.create notification")
@ -460,18 +471,21 @@ class PowerVCCinderManager(service.Service):
# Remove the instance from the local OS
self._unregister_volume_types(context, pvc_vol_type_id)
def _handle_powervc_volume_type_extra_spec_update(self, context, message):
"""
Handle instance state changes sent from PowerVC. This includes
def _handle_powervc_volume_type_extra_spec_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle instance state changes sent from PowerVC. This includes
instance update and all other state changes caused by events like
power on, power off, resize, live migration, and snapshot.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
event_type = message.get('event_type')
LOG.debug("Handling notification: %s" % event_type)
payload = message.get('payload')
pvc_vol_type_id = payload.get('type_id')
if(pvc_vol_type_id is None):
LOG.debug('Null volume type id, ignore extra specs update')
@ -720,16 +734,19 @@ class PowerVCCinderManager(service.Service):
break
return found
def _handle_powervc_volume_create(self, context, message):
"""
Handle volume create messages sent from PowerVC.
def _handle_powervc_volume_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle volume create messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
LOG.debug("Handling notification: %s" % message.get('event_type'))
payload = message.get('payload')
pvc_volume_id = payload.get('volume_id')
# If the volume already exists locally then ignore
@ -751,16 +768,19 @@ class PowerVCCinderManager(service.Service):
LOG.debug('Volume not accessible, ignored!')
return
def _handle_powervc_volume_delete(self, context, message):
"""
Handle volume create messages sent from PowerVC.
def _handle_powervc_volume_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle volume create messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
LOG.debug("Handling notification: %s" % message.get('event_type'))
payload = message.get('payload')
pvc_volume_id = payload.get('volume_id')
# If the volume does not already exist locally then ignore
@ -771,16 +791,19 @@ class PowerVCCinderManager(service.Service):
self._unregister_volumes(context, local_volume.get('id'))
def _handle_powervc_volume_update(self, context, message):
"""
Handle volume create messages sent from PowerVC.
def _handle_powervc_volume_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle volume create messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
LOG.debug("Handling notification: %s" % message.get('event_type'))
payload = message.get('payload')
pvc_volume_id = payload.get('volume_id')
local_volume = self._get_local_volume_by_pvc_id(context, pvc_volume_id)

View File

@ -29,7 +29,7 @@ fake_volume = {'display_name': 'fake_volume',
'instance_uuid': '',
'attach_status': ''}
fake_message = {'payload': {'volume_id': '', 'display_name': ''}}
fake_payload = {'volume_id': '', 'display_name': ''}
fake_context = {}
@ -81,7 +81,8 @@ class Test(unittest.TestCase):
self.moxer.ReplayAll()
self.manager._handle_powervc_volume_create(fake_context, fake_message)
self.manager._handle_powervc_volume_create(context=fake_context,
payload=fake_payload)
self.moxer.UnsetStubs()
self.moxer.VerifyAll()
@ -107,7 +108,8 @@ class Test(unittest.TestCase):
self.moxer.ReplayAll()
self.manager._handle_powervc_volume_create(fake_context, fake_message)
self.manager._handle_powervc_volume_create(context=fake_context,
payload=fake_payload)
self.moxer.UnsetStubs()
self.moxer.VerifyAll()

View File

@ -1,301 +1,132 @@
# Copyright 2013 IBM Corp.
# Copyright 2014 IBM Corp.
"""
This module contains Qpid connection utilities that can be used to connect
to a Qpid message broker and listen for notifications.
Examples:
# Import common messaging module
from powervc.common import messaging
# Connect to host OS Qpid broker and handle instance update notifications.
conn = messaging.LocalConnection(
reconnect_handler=self.handle_qpid_reconnect)
listener = conn.create_listener('nova', 'notifications.info')
listener.register_handler('compute.instance.update',
self._handle_instance_update)
conn.start()
# Connect to PowerVC Qpid broker and handle two event types with a single
# handler function.
conn = messaging.PowerVCConnection()
listener = conn.create_listener('nova', 'notifications.info')
listener.register_handler(['compute.instance.create.start',
'compute.instance.create.end'],
self._handle_instance_create)
conn.start()
# Connect to PowerVC Qpid broker and handle any instance notifications.
conn = messaging.PowerVCConnection()
listener = conn.create_listener('nova', 'notifications.info')
listener.register_handler('compute.instance.*',
self._handle_instance_notifications)
conn.start()
"""This module contains common structures and functions that help to handle
AMQP messages based on olso.messaging framework.
"""
import fnmatch
import json
import sys
import threading
import traceback
import time
from time import sleep
from qpid.messaging import Connection
from qpid.messaging.exceptions import ConnectionError
from powervc.common import config
from powervc.common.gettextutils import _
from oslo.messaging.notify import dispatcher
def log(log, level, msg):
class NotificationEndpoint(object):
"""Message listener endpoint, used to register handler functions, receive
and dispatch notification messages.
"""
Log a message.
MSG_LEVEL = {0: 'AUDIT', 1: 'DEBUG', 2: 'INFO', 3: 'WARN',
4: 'ERROR', 5: 'CRITICAL', 6: 'SAMPLE'}
:param: log The log to write to.
:param: level The logging level for the message
:param: msg The message to log
"""
if not log:
return
if level == 'critical':
log.critical(msg)
elif level == 'error':
log.error(msg)
elif level == 'warn':
log.warn(msg)
elif level == 'info':
log.info(msg)
elif level == 'debug':
log.debug(msg)
def __init__(self, log=None, sec_context=None):
"""Create a NotificationEndpoint object, the core part of a listener.
class QpidConnection(object):
"""
This class represents a connection to a Qpid broker. A QpidConnection must
be created in order to send or receive AMQP messages using a Qpid broker.
"""
def __init__(self, url, username, password, transport='tcp',
reconnection_interval=60, reconnect_handler=None,
context=None, log=None):
:param: log logger used when handle messages.
:param: sec_context this is a security context contains keystone auth.
token for API access, not the context sent by message notifier.
"""
Create a new connection to a Qpid message broker in order to send or
receive AMQP messages.
:param: url URL for the Qpid connection, e.g. 9.10.49.164:5672
:param: username Qpid username
:param: password Qpid password
:param: transport Transport mechanism, one of tcp, tcp+tls,
or ssl (alias for tcp+tls).
:param: reconnection_interval Interval in seconds between reconnect
attempts.
:param: reconnect_handler The function to call upon reconnecting to
the Qpid broker after connection was lost and
then reestablished. This function will be called after the
connections is reestablished but before the listeners are
started up again. It is not passed any parameters.
:param: context The security context
:param: log The logging module used for logging messages. If not
provided then no logging will be done.
"""
self.url = url
self.username = username
self.password = password
self.context = context
self.log = log.getLogger(__name__) if log else None
self.transport = transport
self.reconnection_interval = reconnection_interval
self.reconnect_handler = reconnect_handler
self._listeners = []
self._is_connected = False
def create_listener(self, exchange, topic):
"""
Create a new listener on the given exchange for the given topic.
:param: exchange The name of the Qpid exchange, e.g. 'nova'
:param: topic The topic to listen for, e.g. 'notifications.info'
:returns: A new QpidListener that will listen for messages on the
given exchange and topic.
"""
listener = QpidListener(self, exchange, topic)
self._listeners.append(listener)
return listener
def start(self, is_reconnect=False):
"""
Initiate the Qpid connection and start up any listeners.
:param: is_reconnect True if this method is called as part of a
reconnect attempt, False otherwise
:raise: ConnectionError if a connection cannot be established
"""
# If the Qpid broker URL is not specified (or just the hostname is not
# specified) then we can't make a connection.
if not self.url or self.url.startswith(':'):
log(self.log, 'warn', _('Qpid broker not specified, cannot start '
'connection.'))
return
if not self._is_connected:
self.conn = Connection(self.url, username=self.username,
password=self.password,
transport=self.transport)
try:
self.conn.open()
except ConnectionError as e:
log(self.log, 'critical', _('Cannot connect to Qpid message '
'broker: %s') % (e.message))
# close this connection when encounter connection error
# otherwise, it will leave an ESTABLISHED connection
# to qpid server forever.
if self.conn is not None:
self.conn.close()
raise e
self._is_connected = True
if is_reconnect and self.reconnect_handler:
self.reconnect_handler()
for listener in self._listeners:
listener._start(self.conn)
log(self.log, 'info', _('Connected to Qpid message broker: '
'%s@%s') % (self.username, self.url))
def _reconnect(self):
"""
Attempt to reconnect to the Qpid message broker in intervals until the
connection comes back.
"""
self.conn = None
class ReconnectionThread(threading.Thread):
def __init__(self, qpid_connection):
super(ReconnectionThread, self).__init__(
name='ReconnectionThread')
self.qpid_connection = qpid_connection
def run(self):
while not self.qpid_connection._is_connected:
try:
self.qpid_connection.start(is_reconnect=True)
except ConnectionError:
sleep(self.qpid_connection.reconnection_interval)
pass
reconnection_thread = ReconnectionThread(self)
reconnection_thread.start()
def set_reconnect_handler(self, reconnect_handler):
"""
Set the function to call upon reconnecting to the Qpid broker after
connection is lost and then reestablished.
:param: reconnect_handler The function to call upon reconnecting.
"""
self.reconnect_handler = reconnect_handler
class PowerVCConnection(QpidConnection):
"""
This class represents a connection to the PowerVC Qpid broker as defined
in the configuration property files.
"""
def __init__(self, reconnect_handler=None, context=None, log=None):
"""
Create a new connection to the PowerVC Qpid message broker in order
to send or receive AMQP messages.
:param: reconnect_handler The function to call upon reconnecting to
the Qpid broker after connection was lost and
then reestablished. This function will be called after the
connection is reestablished but before the listeners are
started up again. It is not passed any parameters.
:param: context The security context
:param: log The logging module used for logging messages. If not
provided then no logging will be done.
"""
if config.AMQP_POWERVC_CONF.qpid_protocol == 'ssl':
transport = 'ssl'
else:
transport = 'tcp'
super(PowerVCConnection,
self).__init__('%s:%d' % (
config.AMQP_POWERVC_CONF.qpid_hostname,
config.AMQP_POWERVC_CONF.qpid_port),
config.AMQP_POWERVC_CONF.qpid_username,
config.AMQP_POWERVC_CONF.qpid_password,
reconnect_handler=reconnect_handler,
context=context, log=log, transport=transport)
class LocalConnection(QpidConnection):
"""
This class represents a connection to the local OS Qpid broker as defined
in the configuration property files.
"""
def __init__(self, reconnect_handler=None, context=None, log=None):
"""
Create a new connection to the local OS Qpid message broker in order
to send or receive AMQP messages.
:param: reconnect_handler The function to call upon reconnecting to
the Qpid broker after connection was lost and
then reestablished. This function will be called after the
connection is reestablished but before the listeners are
started up again. It is not passed any parameters.
:param: context The security context
:param: log The logging module used for logging messages. If not
provided then no logging will be done.
"""
if config.AMQP_OPENSTACK_CONF.qpid_protocol == 'ssl':
transport = 'ssl'
else:
transport = 'tcp'
super(LocalConnection,
self).__init__('%s:%d' % (
config.AMQP_OPENSTACK_CONF.qpid_hostname,
config.AMQP_OPENSTACK_CONF.qpid_port),
config.AMQP_OPENSTACK_CONF.qpid_username,
config.AMQP_OPENSTACK_CONF.qpid_password,
reconnect_handler=reconnect_handler,
context=context, log=log, transport=transport)
class QpidListener(object):
'''
This class is used to listen for AMQP message notifications. It should
probably not be instantiated directly. First create a QpidConnection and
then add a QpidListener to the connection using the
QpidConnection.create_listener() method.
'''
def __init__(self, qpid_connection, exchange, topic):
"""
Create a new QpidListener object to listen for AMQP messages.
:param: qpid_connection The QpidConnection object used for connecting
to the Qpid message broker.
:param: exchange The name of the Qpid exchange, e.g. 'nova'
:param: topic The topic to listen for, e.g. 'notifications.info'
"""
self.qpid_connection = qpid_connection
self.exchange = exchange
self.topic = topic
self._handler_map = {}
self._count_since_acknowledge = 0
self._log = log
self._sec_ctxt = sec_context
def audit(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at audit level."""
return self._dispatch(0, ctxt, publisher_id,
event_type, payload, metadata)
def debug(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at debug level."""
return self._dispatch(1, ctxt, publisher_id,
event_type, payload, metadata)
def info(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at info level."""
return self._dispatch(2, ctxt, publisher_id,
event_type, payload, metadata)
def warn(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at warning level."""
return self._dispatch(3, ctxt, publisher_id,
event_type, payload, metadata)
def error(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at error level."""
return self._dispatch(4, ctxt, publisher_id,
event_type, payload, metadata)
def critical(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at critical level."""
return self._dispatch(5, ctxt, publisher_id,
event_type, payload, metadata)
def sample(self, ctxt, publisher_id, event_type, payload, metadata):
"""Receive a notification at sample level.
Sample notifications are for high-frequency events
that typically contain small payloads. eg: "CPU = 70%"
Not all drivers support the sample level
(log, for example) so these could be dropped.
"""
return self._dispatch(6, ctxt, publisher_id,
event_type, payload, metadata)
def _dispatch(self, level, ctxt, publisher_id, event_type, payload,
metadata):
"""Route message to handlers according event_type registered.
"""
handlers = self._get_handlers(event_type)
try:
if handlers:
if self._log and self._log.isEnabledFor('INFO'):
self._log.info("'%s' level '%s' type message is received. "
"Routing to handlers..."
% (self.MSG_LEVEL[level], event_type)
)
for handler in handlers:
start_time = time.time()
handler(context=self._sec_ctxt,
ctxt=ctxt,
event_type=event_type,
payload=payload,
)
end_time = time.time()
if self._log and self._log.isEnabledFor('DEBUG'):
self._log.debug("handler '%s' uses '%f' time(s)"
% (handler, end_time - start_time)
)
return dispatcher.NotificationResult.HANDLED
except Exception:
self._log.exception("Error handling '%(level)s' level '%(type)s' "
"type message '%(msg)s'."
% {'level': self.MSG_LEVEL[level],
'type': event_type,
'msg': payload,
}
)
# TODO(gpanda): consider if requeue is needed in the future,
# not all transport drivers implement support for requeueing, if
# the driver does not support requeueing, it will raise
# NotImplementedError. As far as I tested(oslo.messaging 1.3.1 +
# qpidd 0.14), it doesn't support.
# return dispatcher.NotificationResult.REQUEUE
finally:
pass
def _get_handlers(self, event_type):
"""Get a list of all the registered handlers that match the given event
type.
"""
handlers = []
for event_type_pattern in self._handler_map:
if fnmatch.fnmatch(event_type, event_type_pattern):
handlers.append(self._handler_map.get(event_type_pattern))
return handlers
def register_handler(self, event_type, handler):
"""
Register a handler function for one or more message notification event
types. The handler function will be called when a message is
"""Register a handler function for one or more message notification
event types. The handler function will be called when a message is
received that matches the event type. The handler function will be
passed two arguments: the security context and a dictionary containing
the message attributes. The message attributes include: event_type,
@ -333,173 +164,15 @@ class QpidListener(object):
for et in event_type:
self._handler_map[et] = handler
def unregister_handler(self, event_type):
"""
Stop handling the given message notification event type.
:param: event_type The event type to unregister
"""
try:
self._handler_map.pop(event_type)
except KeyError:
log(self.qpid_connection.log, 'warn',
_('There is no handler for this event type: %s') % event_type)
def start_notification_listener(notification_listener):
def _run():
notification_listener.start()
notification_listener.wait()
def _start(self, connection):
"""
Start listening for messages. This method should probably not be called
directly. After creating a QpidConnection and adding listeners using
the create_listener() method, use the QpidConnection.start() method to
start listening for messages. The QpidConnection will start up all of
the listeners.
:param: connection The qpid.messaging.endpoints.Connection object used
to establish the connection to the message broker.
"""
self.session = connection.session('%s/%s' %
(self.exchange, self.topic))
addr_opts = {
"create": "always",
"node": {
"type": "topic",
"x-declare": {
"durable": True,
"auto-delete": True
},
},
}
connection_info = "%s / %s ; %s" % (self.exchange, self.topic,
json.dumps(addr_opts))
self.receiver = self.session.receiver(connection_info)
log(self.qpid_connection.log, 'debug',
_('QpidListener session info: %s') % (json.dumps(connection_info)))
"""
A listener blocks while it waits for the next message on the queue,
so we initiate a thread to run the listening function.
"""
t = threading.Thread(target=self._listen)
t.start()
def _has_more_messages(self):
'''
Determine if there are any new messages in the queue.
:returns: True if there are messages on the queue, False otherwise
'''
return bool(self.receiver)
def _next_message(self):
'''
Wait for the next message on the queue.
:returns: The raw message object from the message queue
'''
return self.receiver.fetch()
def _acknowledge(self):
'''
Acknowledge a message has been received.
'''
self.session.acknowledge(sync=False)
def _get_handlers(self, event_type):
"""
Get a list of all the registered handlers that match the given event
type.
"""
handlers = []
for event_type_pattern in self._handler_map:
if fnmatch.fnmatch(event_type, event_type_pattern):
handlers.append(self._handler_map.get(event_type_pattern))
return handlers
def _dispatch(self, message):
'''
Dispatch a message to its specific handler.
:param: message A dictionary containing the OpenStack message
notification attributes (event_type, timestamp,
message_id, priority, publisher_id, payload)
'''
event_type = message.get('event_type')
handlers = self._get_handlers(event_type)
log_ = self.qpid_connection.log
self._count_since_acknowledge += 1
try:
if handlers:
log(log_, 'debug', _('Dispatching message to handlers'))
log(log_, 'info', _('Qpid listener received '
'message of event type: %s'
% message['event_type']))
for handler in handlers:
handler(self.qpid_connection.context, message)
except Exception, e:
log(log_, 'error', _('Error handling message: %s: %s. Message: '
'%s.') % (Exception, e, message))
# Print stack trace
exc_type, exc_value, exc_traceback = sys.exc_info()
log(log_, 'error', _('error type %s') % (exc_type))
log(log_, 'error', _('error object %s') % (exc_value))
log(log_, 'error', ''.join(traceback.format_tb(exc_traceback)))
finally:
if self._count_since_acknowledge > 100:
self._count_since_acknowledge = 0
self._acknowledge()
def _resolve_message(self, raw_message):
'''
Resolves the given raw message obtained from the Qpid message queue
into a message that can be dispatched to a handler function.
:param: raw_message A raw message obtained from the Qpid message
queue
:returns: A dictionary containing the following keys:
event_type, timestamp, message_id, priority, publisher_id, payload
'''
content_type = raw_message.content_type
if content_type == 'application/json; charset=utf8':
content = json.loads(raw_message.content)
elif content_type == 'amqp/map':
content = raw_message.content
else:
log(self.qpid_connection.log,
'warn',
_('Qpid listener received unsupported message: '
'%s\nwith content_type %s') % (raw_message.content,
content_type))
return None
message = dict()
for attr in ['event_type', 'timestamp', 'message_id', 'priority',
'publisher_id', 'payload']:
message[attr] = content.get(attr)
log(self.qpid_connection.log, 'debug', _('Qpid listener received '
'message: %s') % (message))
return message
def _listen(self):
'''
Handle messages when they arrive on the message queue.
'''
while True:
try:
if self._has_more_messages():
raw_message = self._next_message()
message = self._resolve_message(raw_message)
if message is None:
continue
self._dispatch(message)
else:
break
except ConnectionError, e:
log(self.qpid_connection.log, 'warn',
_("Connection error: %s") % (e))
self.qpid_connection._is_connected = False
self.qpid_connection._reconnect()
break
except Exception, e:
log(self.qpid_connection.log, 'warn',
_("Unknown error happens for event listener: %s") % (e))
"""
A listener blocks while it waits for the next message on the queue,
so we initiate a thread to run the listening function.
"""
t = threading.Thread(target=_run)
t.start()

View File

@ -1,56 +0,0 @@
# Copyright 2013 IBM Corp.
import unittest
from powervc.common.messaging import QpidConnection
class QpidTest(unittest.TestCase):
def setUp(self):
super(QpidTest, self).setUp()
self.conn = QpidConnection(url='127.0.0.1:5989',
username='test_username',
password='test_passwd',
transport='tcp',
reconnection_interval=60,
reconnect_handler=None,
context=None,
log=None)
def test_create_listener(self):
self.listener = self.conn.\
create_listener('test_exchange', 'test_topic')
self.assertNotEqual(self.listener, None)
self.assertEqual([self.listener], self.conn._listeners)
def test_register_handler(self):
def _fake_handler():
pass
if not hasattr(self, 'listener'):
self.listener = self.conn.\
create_listener('test_exchange', 'test_topic')
self.listener.register_handler('foo.bar.*', _fake_handler)
self.assertEqual(self.listener._handler_map['foo.bar.*'],
_fake_handler)
def test_unregister_handler(self):
def _fake_handler():
pass
if not hasattr(self, 'listener'):
self.listener = self.conn.\
create_listener('test_exchange', 'test_topic')
self.listener.register_handler('foo.bar.*', _fake_handler)
self.assertEqual(self.listener._handler_map['foo.bar.*'],
_fake_handler)
self.listener.unregister_handler('foo.bar.*')
self.assertEqual(self.listener._handler_map,
{})
def tearDown(self):
unittest.TestCase.tearDown(self)
if __name__ == "__main__":
unittest.main()

View File

@ -66,6 +66,9 @@ LOCAL = 'local'
EVENT_TYPE = 'type'
EVENT_CONTEXT = 'context'
EVENT_MESSAGE = 'message'
EVENT_PAYLOAD = 'payload'
REAL_EVENT_TYPE = 'real_type'
REAL_EVENT_CONTEXT = 'ctxt'
# Event queue event types
LOCAL_IMAGE_EVENT = LOCAL
@ -77,7 +80,7 @@ STARTUP_SCAN_EVENT = 'startup'
IMAGE_EVENT_EXCHANGE = 'glance'
# Image notification event topic
IMAGE_EVENT_TOPIC = 'notifications.info'
IMAGE_EVENT_TOPIC = 'notifications'
# Image notification event types
IMAGE_EVENT_TYPE_ALL = 'image.*'

View File

@ -11,7 +11,6 @@ import Queue
import threading
import itertools
from operator import itemgetter
import HTMLParser
from powervc.common import config
@ -23,7 +22,6 @@ from glanceclient.exc import CommunicationError
from glanceclient.exc import HTTPNotFound
from powervc.common import constants as consts
from powervc.common import messaging
from powervc.common.exception import StorageConnectivityGroupNotFound
from powervc.common.gettextutils import _
from powervc.common.client import factory as clients
@ -31,6 +29,12 @@ from powervc.glance.common import constants
from powervc.glance.common import config as glance_config
from powervc.common import utils
from powervc.common import messaging
from oslo.messaging.notify import listener
from oslo.messaging import target
from oslo.messaging import transport
CONF = glance_config.CONF
LOG = logging.getLogger(__name__)
@ -2305,9 +2309,8 @@ class PowerVCImageManager(service.Service):
self._start_pvc_event_handler()
def _start_local_event_handler(self):
"""
Start the local hosting OS image notification event handler if it's not
already running.
"""Start the local hosting OS image notification event handler if it's
not already running.
The event handler is not started if the qpid_hostname is not specified
in the configuration.
@ -2317,45 +2320,35 @@ class PowerVCImageManager(service.Service):
if self.local_event_handler_running:
return
def local_event_reconnect_handler():
"""
The reconnect handler will start a periodic scan operation.
"""
LOG.info(_("Processing local event handler reconnection..."))
self._add_periodic_sync_to_queue()
LOG.debug("Enter _start_local_event_handler method")
try:
trans = transport.get_transport(config.AMQP_OPENSTACK_CONF)
targets = [
target.Target(exchange=constants.IMAGE_EVENT_EXCHANGE,
topic=constants.IMAGE_EVENT_TOPIC)
]
endpoint = messaging.NotificationEndpoint(log=LOG)
# See if the host is specified. If not, do not attempt to connect
# and register the event handler
host = config.AMQP_OPENSTACK_CONF.qpid_hostname
if host and host is not None:
local_conn = messaging.LocalConnection(
reconnect_handler=local_event_reconnect_handler,
log=logging)
local_listener = local_conn.create_listener(
constants.IMAGE_EVENT_EXCHANGE,
constants.IMAGE_EVENT_TOPIC)
endpoint.register_handler(constants.IMAGE_EVENT_TYPE_ALL,
self._local_image_notifications)
# Register the handler to begin processing messages
local_listener.register_handler(
constants.IMAGE_EVENT_TYPE_ALL,
self._local_image_notifications)
local_conn.start()
LOG.info(_('Monitoring local hosting OS for Image '
'notification events...'))
self.local_event_handler_running = True
else:
LOG.warning(_('Local hosting OS image event handling could '
'not be started because the qpid_host was not '
'specified in the configuration file.'))
except Exception as e:
LOG.exception(_('An error occurred starting the local hosting OS '
'image notification event handler: %s'), e)
endpoints = [
endpoint,
]
LOG.debug("Starting to listen...... ")
local_glance_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(local_glance_listener)
LOG.debug("Exit _start_local_event_handler method")
self.local_event_handler_running = True
def _start_pvc_event_handler(self):
"""
Start the PowerVC image notification event handler if not already
"""Start the PowerVC image notification event handler if not already
running.
The event handler is not started if the powervc_qpid_hostname is
@ -2366,41 +2359,32 @@ class PowerVCImageManager(service.Service):
if self.pvc_event_handler_running:
return
def pvc_event_reconnect_handler():
"""
The reconnect handler will start a periodic scan operation.
"""
LOG.info(_("Processing PowerVC event handler reconnection..."))
self._add_periodic_sync_to_queue()
LOG.debug("Enter _start_pvc_event_handler method")
try:
trans = transport.get_transport(config.AMQP_POWERVC_CONF)
targets = [
target.Target(exchange=constants.IMAGE_EVENT_EXCHANGE,
topic=constants.IMAGE_EVENT_TOPIC)
]
endpoint = messaging.NotificationEndpoint(log=LOG)
# See if the host is specified. If not, do not attempt to connect
# and register the event handler
host = config.AMQP_POWERVC_CONF.qpid_hostname
if host and host is not None:
pvc_conn = messaging.PowerVCConnection(
reconnect_handler=pvc_event_reconnect_handler, log=logging)
pvc_listener = pvc_conn.create_listener(
constants.IMAGE_EVENT_EXCHANGE,
constants.IMAGE_EVENT_TOPIC)
endpoint.register_handler(constants.IMAGE_EVENT_TYPE_ALL,
self._pvc_image_notifications)
# Register the handler to begin processing messages
pvc_listener.register_handler(
constants.IMAGE_EVENT_TYPE_ALL,
self._pvc_image_notifications)
pvc_conn.start()
LOG.info(_('Monitoring PowerVC for Image notification '
'events...'))
self.pvc_event_handler_running = True
else:
LOG.warning(_('PowerVC image event handling could not be '
'started because the powervc_qpid_host was not '
'specified in the configuration file.'))
endpoints = [
endpoint,
]
except Exception as e:
LOG.exception(_('An error occurred starting the PowerVC image '
'notification event handler: %s'), e)
LOG.debug("Starting to listen...... ")
pvc_glance_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(pvc_glance_listener)
LOG.debug("Exit _start_pvc_event_handler method")
self.pvc_event_handler_running = True
def _process_event_queue(self):
"""
@ -2421,17 +2405,28 @@ class PowerVCImageManager(service.Service):
str(self.local_events_to_ignore_dict))
LOG.debug(_('pvc events to ignore: %s'),
str(self.pvc_events_to_ignore_dict))
event_type = event.get(constants.EVENT_TYPE)
context = event.get(constants.EVENT_CONTEXT)
message = event.get(constants.EVENT_MESSAGE)
event_type = event.get(constants.EVENT_TYPE)
ctxt = event.get(constants.REAL_EVENT_CONTEXT)
real_type = event.get(constants.REAL_EVENT_TYPE)
payload = event.get(constants.EVENT_PAYLOAD)
if event_type == constants.LOCAL_IMAGE_EVENT:
LOG.debug(_('Processing a local hostingOS image event on '
'the event queue: %s'), str(event))
self._handle_local_image_notifications(context, message)
self.\
_handle_local_image_notifications(context=context,
ctxt=ctxt,
event_type=real_type,
payload=payload,
)
elif event_type == constants.PVC_IMAGE_EVENT:
LOG.debug(_('Processing a PowerVC image event on '
'the event queue: %s'), str(event))
self._handle_pvc_image_notifications(context, message)
self._handle_pvc_image_notifications(context=context,
ctxt=ctxt,
event_type=real_type,
payload=payload,
)
elif event_type == constants.PERIODIC_SCAN_EVENT:
LOG.debug(_('Processing a periodic sync event on '
'the event queue: %s'), str(event))
@ -2449,23 +2444,35 @@ class PowerVCImageManager(service.Service):
finally:
self.event_queue.task_done()
def _local_image_notifications(self, context, message):
"""
Place the local image event on the event queue for processing.
def _local_image_notifications(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Place the local image event on the event queue for processing.
:param: context The event security context
:param: message The event message
:param: context The security context
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
event = {}
event[constants.EVENT_TYPE] = constants.LOCAL_IMAGE_EVENT
event[constants.EVENT_CONTEXT] = context
event[constants.EVENT_MESSAGE] = message
event[constants.REAL_EVENT_CONTEXT] = ctxt
event[constants.REAL_EVENT_TYPE] = event_type
event[constants.EVENT_PAYLOAD] = payload
LOG.debug(_('Adding local image event to event queue: %s'), str(event))
self.event_queue.put(event)
def _handle_local_image_notifications(self, context, message):
"""
Handle image notification events received from the local hosting OS.
def _handle_local_image_notifications(self,
context=None,
ctxt=None,
event_type=None,
payload=None,
):
"""Handle image notification events received from the local hosting OS.
Only handle update, and delete event types. The activate event
is processed, but only to add the new image to the update_at dict.
@ -2474,14 +2481,13 @@ class PowerVCImageManager(service.Service):
event from PowerVC to the ignore list. Then when that event arrives
from PowerVC because of this update we will ignore it.
:param: context The event security context
:param: message The event message
:param: context The security context
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
if message is None:
LOG.debug(_('The local image event notification had no message!'))
return
event_type = message.get('event_type')
v1image_dict = message.get('payload')
v1image_dict = payload
if event_type == constants.IMAGE_EVENT_TYPE_UPDATE:
self._process_local_image_update_event(v1image_dict)
elif event_type == constants.IMAGE_EVENT_TYPE_DELETE:
@ -2491,7 +2497,11 @@ class PowerVCImageManager(service.Service):
elif event_type == constants.IMAGE_EVENT_TYPE_CREATE:
self._process_local_image_create_event(v1image_dict)
else:
LOG.debug(_('Did not process event: %s'), str(message))
LOG.debug(_("Did not process event: type:'%(event_type)s' type, "
"payload:'%(payload)s'"
)
% (event_type, payload)
)
def _process_local_image_update_event(self, v1image_dict):
"""
@ -2815,24 +2825,37 @@ class PowerVCImageManager(service.Service):
'\'%s\'. The PowerVC UUID is not known.'),
local_name)
def _pvc_image_notifications(self, context, message):
"""
Place the PowerVC image event on the event queue for processing.
def _pvc_image_notifications(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Place the PowerVC image event on the event queue for processing.
:param: context The event security context
:param: message The event message
:param: context The security context
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
event = {}
event[constants.EVENT_TYPE] = constants.PVC_IMAGE_EVENT
event[constants.EVENT_CONTEXT] = context
event[constants.EVENT_MESSAGE] = message
event[constants.REAL_EVENT_CONTEXT] = ctxt
event[constants.REAL_EVENT_TYPE] = event_type
event[constants.EVENT_PAYLOAD] = payload
LOG.debug(_('Adding PowerVC image event to event queue: %s'),
str(event))
self.event_queue.put(event)
def _handle_pvc_image_notifications(self, context, message):
"""
Handle image notification events received from PowerVC.
def _handle_pvc_image_notifications(self,
context=None,
ctxt=None,
event_type=None,
payload=None,
):
"""Handle image notification events received from PowerVC.
Only handle activate, update, and delete event types.
There is a scheme in place to keep events from ping-ponging back
@ -2841,15 +2864,13 @@ class PowerVCImageManager(service.Service):
that event arrives from the hosting OS because of this update we
will ignore it.
:param: context The event security context
:param: message The event message
:param: context The security context
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
if message is None:
LOG.debug(_('The PowerVC image event notification had no '
'message!'))
return
event_type = message.get('event_type')
v1image_dict = message.get('payload')
v1image_dict = payload
if event_type == constants.IMAGE_EVENT_TYPE_UPDATE:
self._process_pvc_image_update_event(v1image_dict)
elif event_type == constants.IMAGE_EVENT_TYPE_DELETE:
@ -2857,7 +2878,11 @@ class PowerVCImageManager(service.Service):
elif event_type == constants.IMAGE_EVENT_TYPE_ACTIVATE:
self._process_pvc_image_activate_event(v1image_dict)
else:
LOG.debug(_('Did not process event: %s'), str(message))
LOG.debug(_("Did not process event: type:'%(event_type)s' type, "
"payload:'%(payload)s'"
)
% (event_type, payload)
)
def _process_pvc_image_update_event(self, v1image_dict):
"""

View File

@ -1,246 +0,0 @@
# Copyright 2013 IBM Corp.
import sys
import itertools
import time
import traceback
from oslo.config import cfg
from glance.openstack.common import gettextutils
gettextutils.install('glance')
import glance.openstack.common.log as logging
from glance.common import config as logging_config
from glanceclient.v1 import images as v1images
from powervc.common import config
from powervc.glance.common import constants
# PowerVC Driver ImageManager specific configuration
image_opts = [
# The image period sync interval in seconds
cfg.IntOpt('image_periodic_sync_interval_in_seconds',
default=constants.IMAGE_PERIODIC_SYNC_INTERVAL_IN_SECONDS)
]
CONF = config.CONF
CONF.register_opts(image_opts, 'powervc')
LOG = logging.getLogger(__name__)
config.parse_power_config(sys.argv, 'glance')
from powervc.common import messaging
from powervc.common import constants as consts
import powervc.common.client.factory as clients
def test_image_events(wait_forever=True):
def local_reconnect():
LOG.debug(_('Re-established connection to local hosting OS '
'Qpid broker'))
local_conn = messaging.LocalConnection(log=logging,
reconnect_handler=local_reconnect)
# local_conn = messaging.QpidConnection('localhost:5672', \
# 'admin', 'ICA1NTQxNzI5ODgK')
# conn = messaging.QpidConnection('localhost:5672', 'admin', 'openstack1')
local_listener = local_conn.create_listener('glance', 'notifications.info')
local_listener.register_handler('image.*',
handle_local_image_notifications)
local_conn.start()
# pvc_conn = messaging.QpidConnection('9.5.125.55:5672', \
# 'anonymous', '')
def pvc_reconnect():
LOG.debug(_('Re-established connection to PowerVC Qpid broker'))
pvc_conn = messaging.PowerVCConnection(log=logging,
reconnect_handler=pvc_reconnect)
# pvc_conn = messaging.QpidConnection('9.5.125.55:5672', \
# 'root', 'passw0rd')
pvc_listener = pvc_conn.create_listener('glance', 'notifications.info')
pvc_listener.register_handler('image.*',
handle_pvc_image_notifications)
pvc_conn.start()
print 'Monitoring hosting OS and PowerVC for Image notifications...'
while wait_forever:
time.sleep(5)
def test_pvc_image_events(wait_forever=True):
# pvc_conn = messaging.QpidConnection('9.5.125.55:5672', \
# 'anonymous', '')
def pvc_reconnect():
LOG.debug(_('Re-established connection to PowerVC Qpid broker'))
pvc_conn = messaging.PowerVCConnection(log=logging,
reconnect_handler=pvc_reconnect)
# pvc_conn = messaging.QpidConnection('9.5.125.55:5672', \
# 'root', 'passw0rd')
pvc_listener = pvc_conn.create_listener('glance', 'notifications.info')
pvc_listener.register_handler('image.*',
handle_pvc_image_notifications)
pvc_conn.start()
print 'Monitoring PowerVC for Image notifications...'
while wait_forever:
time.sleep(5)
def handle_local_image_notifications(context, message):
print '=' * 80
print 'LOCAL:', str(context)
print 'LOCAL:', str(message)
image = message.get('payload') # should be the v1 image as a dict
dump_image(image)
print '=' * 80
def handle_pvc_image_notifications(context, message):
print '=' * 80
print 'PVC:', str(context)
print 'PVC:', str(message)
image = message.get('payload') # should be the v1 image as a dict
dump_image(image)
print '=' * 80
def dump_image(image_dict):
for v1imagekey in image_dict.keys():
print v1imagekey, '=', image_dict.get(v1imagekey)
props = image_dict.get('properties')
if props:
for v1imageprop in props.keys():
print 'property: ', v1imageprop, '=',\
props.get(v1imageprop)
def test_update_local_image(image_id):
params = {}
filters = {}
filters['is_public'] = False
params['filters'] = filters
local_v1client = \
clients.LOCAL.get_client(str(consts.SERVICE_TYPES.image), 'v1')
v1local_images = local_v1client.images
image = \
get_v1image_from_id(image_id, itertools.chain(
v1local_images.list(), v1local_images.list(**params)))
if image:
field_dict, patch_dict = get_v1image_update_fields(image)
if 'is_public' in field_dict.keys():
public = field_dict['is_public']
field_dict['is_public'] = not public
v1local_images.update(image, **field_dict)
if len(patch_dict) > 0:
local_v2client = \
clients.LOCAL.get_client(str(consts.SERVICE_TYPES.image), 'v2')
v2local_images = local_v2client.images
v2local_images.update(image.id, **patch_dict)
print 'Image', image.name, 'updated.'
else:
print 'Image', image_id, 'not found!'
def get_v1image_update_fields(image):
"""
Get the properties for an image update
:param: image The image to pull properties from to be used
for an image update operation.
:returns: A tuple containing with the dict containing the
properties to use for an image update operation,
and the dict of the properties that are too
large to be processed by v1 Image APIs. Those
properties should be updated using the
v2 Image PATCH API.
"""
field_dict = {}
patch_dict = {}
props = image.properties
if props and props is not None:
patch_dict = remove_large_properties(props)
image.properties = props
image_dict = image.to_dict()
for imagekey in image_dict.keys():
if imagekey in v1images.UPDATE_PARAMS and \
imagekey not in constants.IMAGE_UPDATE_PARAMS_FILTER:
field_value = image_dict.get(imagekey)
if field_value is not None:
if len(str(field_value)) < constants.MAX_HEADER_LEN_V1:
field_dict[imagekey] = field_value
else:
patch_dict[imagekey] = field_value
return field_dict, patch_dict
def remove_large_properties(properties):
"""
Remove any properties that are too large to be processed by
the v1 APIs and return them in a dict to the caller. The properties
passed in are also modified.
:param: properties. The properties dict to remove large properties
from. Large properties are removed from the original
properties dict
:returns: A dict containing properties that are too large to
be processed by v1 Image APIs
"""
too_large_properties = {}
if properties and properties is not None:
for propkey in properties.keys():
propvalue = properties.get(propkey)
if propvalue and propvalue is not None:
if properties.get(propkey) and (len(str(propvalue)) >=
constants.MAX_HEADER_LEN_V1):
too_large_properties[propkey] = properties.pop(propkey)
return too_large_properties
def test_delete_local_image(image_id):
pass
def get_v1image_from_id(image_id, v1images):
"""
Get a v1 Image from an image id.
:param: image_id The image id
:param: v1images The image manager used to obtain images from the
v1 glance client
:returns: The image for the specified id or None if not found.
"""
for image in v1images:
if image and image.id == image_id:
return image
return None
"""
Main test entry point
"""
if __name__ == '__main__':
try:
# turn off debug logging
# CONF.debug = False
logging_config.setup_logging()
logging.setup('powervc')
# test getting the staging project id
# test_image_events(wait_forever=True)
test_pvc_image_events(wait_forever=True)
# image_id = '3060d198-c951-4693-9b1d-6314ac0539bf'
# test_update_local_image(image_id)
# test_delete_local_image(image_id)
print 'Tests done!'
except Exception:
traceback.print_exc()
raise

View File

@ -2,45 +2,6 @@
debug = False
verbose = True
# The messaging module to use, defaults to kombu.
# rpc_backend = neutron.openstack.common.rpc.impl_kombu
# AMQP password
# rabbit_password = openstack1
# AMQP host
# rabbit_host = localhost
# Size of RPC thread pool
# rpc_thread_pool_size = 64
# Size of RPC connection pool
# rpc_conn_pool_size = 30
# Seconds to wait for a response from call or multicall
# rpc_response_timeout = 60
# Seconds to wait before a cast expires (TTL). Only supported by impl_zmq.
# rpc_cast_timeout = 30
# Modules of exceptions that are permitted to be recreated
# upon receiving exception data from an rpc call.
# allowed_rpc_exception_modules = neutron.openstack.common.exception, nova.exception
# AMQP exchange to connect to if using RabbitMQ or QPID
#control_exchange = nova
# QPID
# rpc_backend=neutron.openstack.common.rpc.impl_qpid
# Qpid broker hostname
# qpid_hostname = localhost
# Qpid broker port
# qpid_port = 5672
# Username for qpid connection
# qpid_username = qpidclient
# Password for qpid connection
# qpid_password = openstack1
# Space separated list of SASL mechanisms to use for auth
# qpid_sasl_mechanisms = ''
# Seconds between connection keepalive heartbeats
# qpid_heartbeat = 60
# Transport to use, either 'tcp' or 'ssl'
# qpid_protocol = tcp
# Disable Nagle algorithm
# qpid_tcp_nodelay = True
[AGENT]
# Agent's polling interval in seconds
polling_interval = 60

View File

@ -12,7 +12,6 @@ Created on Aug 1, 2013
from neutron.openstack.common import log as logging
from powervc.common import messaging
from powervc.common.client import factory
from powervc.common.constants import SERVICE_TYPES
from powervc.common.constants import LOCAL_OS
@ -22,6 +21,13 @@ from powervc.neutron.common import constants
from powervc.neutron.common import utils
from powervc.neutron.db import powervc_db_v2
from powervc.common import config as cfg
from powervc.common import messaging
from oslo.messaging.notify import listener
from oslo.messaging import target
from oslo.messaging import transport
LOG = logging.getLogger(__name__)
@ -44,38 +50,54 @@ class Client(neutron_client_bindings.Client):
def _create_amqp_listeners(self):
"""Listen for AMQP messages from the local OS"""
LOG.debug(_('Creating AMQP listeners'))
def reconnect():
LOG.info(_('Re-established connection to local OS Qpid broker'))
self.agent.queue_event(self.os, constants.EVENT_FULL_SYNC, None)
LOG.debug("Enter _create_amqp_listeners(local) method")
connection = messaging.LocalConnection(log=logging,
reconnect_handler=reconnect)
listener = connection.create_listener(constants.QPID_EXCHANGE,
constants.QPID_TOPIC)
listener.register_handler(constants.EVENT_NETWORK_CREATE,
trans = transport.get_transport(cfg.AMQP_OPENSTACK_CONF)
targets = [
target.Target(exchange=constants.QPID_EXCHANGE,
topic=constants.QPID_TOPIC)
]
endpoint = messaging.NotificationEndpoint(log=LOG)
endpoint.register_handler(constants.EVENT_NETWORK_CREATE,
self._handle_network_create)
listener.register_handler(constants.EVENT_NETWORK_UPDATE,
endpoint.register_handler(constants.EVENT_NETWORK_UPDATE,
self._handle_network_update)
listener.register_handler(constants.EVENT_NETWORK_DELETE,
endpoint.register_handler(constants.EVENT_NETWORK_DELETE,
self._handle_network_delete)
listener.register_handler(constants.EVENT_SUBNET_CREATE,
endpoint.register_handler(constants.EVENT_SUBNET_CREATE,
self._handle_subnet_create)
listener.register_handler(constants.EVENT_SUBNET_UPDATE,
endpoint.register_handler(constants.EVENT_SUBNET_UPDATE,
self._handle_subnet_update)
listener.register_handler(constants.EVENT_SUBNET_DELETE,
endpoint.register_handler(constants.EVENT_SUBNET_DELETE,
self._handle_subnet_delete)
listener.register_handler(constants.EVENT_PORT_CREATE,
endpoint.register_handler(constants.EVENT_PORT_CREATE,
self._handle_port_create)
listener.register_handler(constants.EVENT_PORT_UPDATE,
endpoint.register_handler(constants.EVENT_PORT_UPDATE,
self._handle_port_update)
listener.register_handler(constants.EVENT_PORT_DELETE,
endpoint.register_handler(constants.EVENT_PORT_DELETE,
self._handle_port_delete)
connection.start()
def _handle_network_create(self, context, message):
event, payload = self._extact_event_payload(message)
endpoints = [
endpoint,
]
LOG.debug("Starting to listen...... ")
local_neutron_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(local_neutron_listener)
LOG.debug("Exit _create_amqp_listeners(local) method")
def _handle_network_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
network = payload.get('network')
network_id = network.get('id')
if not utils.is_network_mappable(network):
@ -85,20 +107,32 @@ class Client(neutron_client_bindings.Client):
if db_net:
LOG.info(_("DB entry for network %s already exists"), network_id)
return
self.agent.queue_event(self.os, event, network)
self.agent.queue_event(self.os, event_type, network)
def _handle_network_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_network_update(self, context, message):
event, payload = self._extact_event_payload(message)
network = payload.get('network')
self.agent.queue_event(self.os, event, network)
self.agent.queue_event(self.os, event_type, network)
def _handle_network_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_network_delete(self, context, message):
event, payload = self._extact_event_payload(message)
network_id = payload.get('network_id')
self.agent.queue_event(self.os, event, network_id)
self.agent.queue_event(self.os, event_type, network_id)
def _handle_subnet_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_subnet_create(self, context, message):
event, payload = self._extact_event_payload(message)
subnet = payload.get('subnet')
subnet_id = subnet.get('id')
if not utils.is_subnet_mappable(subnet):
@ -108,20 +142,32 @@ class Client(neutron_client_bindings.Client):
if db_sub:
LOG.info(_("DB entry for subnet %s already exists"), subnet_id)
return
self.agent.queue_event(self.os, event, subnet)
self.agent.queue_event(self.os, event_type, subnet)
def _handle_subnet_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_subnet_update(self, context, message):
event, payload = self._extact_event_payload(message)
subnet = payload.get('subnet')
self.agent.queue_event(self.os, event, subnet)
self.agent.queue_event(self.os, event_type, subnet)
def _handle_subnet_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_subnet_delete(self, context, message):
event, payload = self._extact_event_payload(message)
subnet_id = payload.get('subnet_id')
self.agent.queue_event(self.os, event, subnet_id)
self.agent.queue_event(self.os, event_type, subnet_id)
def _handle_port_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_port_create(self, context, message):
event, payload = self._extact_event_payload(message)
port = payload.get('port')
port_id = port.get('id')
if not utils.is_port_mappable(port):
@ -131,17 +177,25 @@ class Client(neutron_client_bindings.Client):
if db_port:
LOG.info(_("DB entry for port %s already exists"), port_id)
return
self.agent.queue_event(self.os, event, port)
self.agent.queue_event(self.os, event_type, port)
def _handle_port_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_port_update(self, context, message):
event, payload = self._extact_event_payload(message)
port = payload.get('port')
self.agent.queue_event(self.os, event, port)
self.agent.queue_event(self.os, event_type, port)
def _handle_port_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_port_delete(self, context, message):
event, payload = self._extact_event_payload(message)
port_id = payload.get('port_id')
self.agent.queue_event(self.os, event, port_id)
self.agent.queue_event(self.os, event_type, port_id)
def get_power_vm_mapping(self):
"""

View File

@ -12,7 +12,6 @@ Created on Aug 1, 2013
from neutron.openstack.common import log as logging
from powervc.common import messaging
from powervc.common.constants import POWERVC_OS
from powervc.common.gettextutils import _
from powervc.neutron.client import neutron_client_bindings
@ -20,6 +19,13 @@ from powervc.neutron.common import constants
from powervc.neutron.common import utils
from powervc.neutron.db import powervc_db_v2
from powervc.common import config as cfg
from powervc.common import messaging
from oslo.messaging.notify import listener
from oslo.messaging import target
from oslo.messaging import transport
LOG = logging.getLogger(__name__)
@ -36,39 +42,55 @@ class Client(neutron_client_bindings.Client):
self._create_amqp_listeners()
def _create_amqp_listeners(self):
"""Listen for AMQP messages from PowerVC"""
LOG.debug(_('Creating AMQP listeners'))
"""Listen for AMQP messages from PowerVC."""
def reconnect():
LOG.info(_('Re-established connection to PowerVC Qpid broker'))
self.agent.queue_event(self.os, constants.EVENT_FULL_SYNC, None)
LOG.debug("Entry _create_amqp_listeners(pvc) method")
connection = messaging.PowerVCConnection(log=logging,
reconnect_handler=reconnect)
listener = connection.create_listener(constants.QPID_EXCHANGE,
constants.QPID_TOPIC)
listener.register_handler(constants.EVENT_NETWORK_CREATE,
trans = transport.get_transport(cfg.AMQP_POWERVC_CONF)
targets = [
target.Target(exchange=constants.QPID_EXCHANGE,
topic=constants.QPID_TOPIC)
]
endpoint = messaging.NotificationEndpoint(log=LOG)
endpoint.register_handler(constants.EVENT_NETWORK_CREATE,
self._handle_network_create)
listener.register_handler(constants.EVENT_NETWORK_UPDATE,
endpoint.register_handler(constants.EVENT_NETWORK_UPDATE,
self._handle_network_update)
listener.register_handler(constants.EVENT_NETWORK_DELETE,
endpoint.register_handler(constants.EVENT_NETWORK_DELETE,
self._handle_network_delete)
listener.register_handler(constants.EVENT_SUBNET_CREATE,
endpoint.register_handler(constants.EVENT_SUBNET_CREATE,
self._handle_subnet_create)
listener.register_handler(constants.EVENT_SUBNET_UPDATE,
endpoint.register_handler(constants.EVENT_SUBNET_UPDATE,
self._handle_subnet_update)
listener.register_handler(constants.EVENT_SUBNET_DELETE,
endpoint.register_handler(constants.EVENT_SUBNET_DELETE,
self._handle_subnet_delete)
listener.register_handler(constants.EVENT_PORT_CREATE,
endpoint.register_handler(constants.EVENT_PORT_CREATE,
self._handle_port_create)
listener.register_handler(constants.EVENT_PORT_UPDATE,
endpoint.register_handler(constants.EVENT_PORT_UPDATE,
self._handle_port_update)
listener.register_handler(constants.EVENT_PORT_DELETE,
endpoint.register_handler(constants.EVENT_PORT_DELETE,
self._handle_port_delete)
connection.start()
def _handle_network_create(self, context, message):
event, payload = self._extact_event_payload(message)
endpoints = [
endpoint,
]
LOG.debug("Starting to listen...... ")
pvc_neutron_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(pvc_neutron_listener)
LOG.debug("Exit _create_amqp_listeners(pvc) method")
def _handle_network_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
network = payload.get('network')
network_id = network.get('id')
if not utils.is_network_mappable(network):
@ -78,20 +100,32 @@ class Client(neutron_client_bindings.Client):
if db_net:
LOG.info(_("DB entry for network %s already exists"), network_id)
return
self.agent.queue_event(self.os, event, network)
self.agent.queue_event(self.os, event_type, network)
def _handle_network_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_network_update(self, context, message):
event, payload = self._extact_event_payload(message)
network = payload.get('network')
self.agent.queue_event(self.os, event, network)
self.agent.queue_event(self.os, event_type, network)
def _handle_network_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_network_delete(self, context, message):
event, payload = self._extact_event_payload(message)
network_id = payload.get('network_id')
self.agent.queue_event(self.os, event, network_id)
self.agent.queue_event(self.os, event_type, network_id)
def _handle_subnet_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_subnet_create(self, context, message):
event, payload = self._extact_event_payload(message)
subnet = payload.get('subnet')
subnet_id = subnet.get('id')
if not utils.is_subnet_mappable(subnet):
@ -101,20 +135,32 @@ class Client(neutron_client_bindings.Client):
if db_sub:
LOG.info(_("DB entry for subnet %s already exists"), subnet_id)
return
self.agent.queue_event(self.os, event, subnet)
self.agent.queue_event(self.os, event_type, subnet)
def _handle_subnet_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_subnet_update(self, context, message):
event, payload = self._extact_event_payload(message)
subnet = payload.get('subnet')
self.agent.queue_event(self.os, event, subnet)
self.agent.queue_event(self.os, event_type, subnet)
def _handle_subnet_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_subnet_delete(self, context, message):
event, payload = self._extact_event_payload(message)
subnet_id = payload.get('subnet_id')
self.agent.queue_event(self.os, event, subnet_id)
self.agent.queue_event(self.os, event_type, subnet_id)
def _handle_port_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_port_create(self, context, message):
event, payload = self._extact_event_payload(message)
port = payload.get('port')
port_id = port.get('id')
if not utils.is_port_mappable(port):
@ -124,14 +170,22 @@ class Client(neutron_client_bindings.Client):
if db_port:
LOG.info(_("DB entry for port %s already exists"), port_id)
return
self.agent.queue_event(self.os, event, port)
self.agent.queue_event(self.os, event_type, port)
def _handle_port_update(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_port_update(self, context, message):
event, payload = self._extact_event_payload(message)
port = payload.get('port')
self.agent.queue_event(self.os, event, port)
self.agent.queue_event(self.os, event_type, port)
def _handle_port_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
def _handle_port_delete(self, context, message):
event, payload = self._extact_event_payload(message)
port_id = payload.get('port_id')
self.agent.queue_event(self.os, event, port_id)
self.agent.queue_event(self.os, event_type, port_id)

View File

@ -51,7 +51,7 @@ PORT_UPDATE_FIELDS = ['name']
# Qpid message handling
QPID_EXCHANGE = 'neutron'
QPID_TOPIC = 'notifications.info'
QPID_TOPIC = 'notifications'
EVENT_END_THREAD = 'thread.end'
EVENT_FULL_SYNC = 'full.sync'

View File

@ -36,13 +36,17 @@ from nova.objects import base as obj_base
from powervc.nova.driver.compute import computes
from powervc.nova.driver.compute import constants
from powervc.nova.driver.compute import task_states as pvc_task_states
from powervc.common import messaging
from powervc.nova.driver.virt.powervc.sync import flavorsync
from powervc import utils
from powervc.common import utils as utills
from powervc.common.gettextutils import _
from powervc.common.client import delegate as ctx_delegate
from powervc.common import messaging
from oslo.messaging.notify import listener
from oslo.messaging import target
from oslo.messaging import transport
LOG = logging.getLogger(__name__)
@ -916,65 +920,69 @@ class PowerVCCloudManager(manager.Manager):
return ['default']
def _create_local_listeners(self, ctx):
"""Listen for local(OpenStack) compute node notifications."""
def reconnect_handler():
LOG.debug(_('Re-established connection to local Qpid broker'))
LOG.debug("Enter _create_local_listeners method")
# Create Qpid connection and listener
conn = messaging.LocalConnection(reconnect_handler=reconnect_handler,
context=ctx,
log=logging)
listener = conn.create_listener('nova', 'notifications.info')
trans = transport.get_transport(cfg.AMQP_OPENSTACK_CONF)
targets = [
target.Target(exchange='nova', topic='notifications')
]
endpoint = messaging.NotificationEndpoint(log=LOG, sec_context=ctx)
# Instance state changes
listener.register_handler([
endpoint.register_handler([
constants.EVENT_INSTANCE_RESIZE,
constants.EVENT_INSTANCE_RESIZE_CONFIRM,
constants.EVENT_INSTANCE_LIVE_MIGRATE],
self._handle_local_deferred_host_updates)
# Instance creation
listener.register_handler(constants.EVENT_INSTANCE_CREATE,
endpoint.register_handler(constants.EVENT_INSTANCE_CREATE,
self._handle_local_instance_create)
endpoints = [
endpoint,
]
conn.start()
LOG.debug("Starting to listen...... ")
local_nova_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(local_nova_listener)
LOG.debug("Exit _create_local_listeners method")
def _create_powervc_listeners(self, ctx):
"""
Listen for out-of-band changes made in PowerVC.
"""Listen for out-of-band changes made in PowerVC.
This method creates the connection to the PowerVC Qpid broker and
sets up handlers so that any changes made directly in PowerVC are
reflected in the local OS.
Any changes made directly in PowerVC will be reflected in the local OS.
:param: ctx The security context
"""
# Function to call if we lose the Qpid connection and then get it back
def reconnect_handler():
LOG.debug(_('Re-established connection to Qpid broker, sync all '
'instances on next sync interval'))
self.full_instance_sync_required = True
# Create Qpid connection and listener
conn = messaging.PowerVCConnection(reconnect_handler=reconnect_handler,
context=ctx,
log=logging)
listener = conn.create_listener('nova', 'notifications.info')
LOG.debug("Enter _create_powervc_listeners method")
trans = transport.get_transport(cfg.AMQP_POWERVC_CONF)
targets = [
target.Target(exchange='nova', topic='notifications')
]
endpoint = messaging.NotificationEndpoint(log=LOG, sec_context=ctx)
# Instance creation
listener.register_handler(constants.EVENT_INSTANCE_CREATE,
endpoint.register_handler(constants.EVENT_INSTANCE_CREATE,
self._handle_powervc_instance_create)
# onboarding end
listener.register_handler(constants.EVENT_INSTANCE_IMPORT,
endpoint.register_handler(constants.EVENT_INSTANCE_IMPORT,
self._handle_powervc_instance_create)
# Instance deletion
listener.register_handler(constants.EVENT_INSTANCE_DELETE,
endpoint.register_handler(constants.EVENT_INSTANCE_DELETE,
self._handle_powervc_instance_delete)
# Instance state changes
listener.register_handler([
endpoint.register_handler([
constants.EVENT_INSTANCE_UPDATE,
constants.EVENT_INSTANCE_POWER_ON,
constants.EVENT_INSTANCE_POWER_OFF,
@ -986,16 +994,30 @@ class PowerVCCloudManager(manager.Manager):
self._handle_powervc_instance_state)
# Instance volume attach/detach event handling
listener.register_handler([
endpoint.register_handler([
constants.EVENT_INSTANCE_VOLUME_ATTACH,
constants.EVENT_INSTANCE_VOLUME_DETACH],
self._handle_volume_attach_or_detach)
conn.start()
endpoints = [
endpoint,
]
def _handle_local_instance_create(self, context, message):
"""
Handle local deployment completed messages sent from the
LOG.debug("Starting to listen...... ")
pvc_nova_listener = listener.\
get_notification_listener(trans, targets, endpoints,
allow_requeue=False)
messaging.start_notification_listener(pvc_nova_listener)
LOG.debug("Exit _create_powervc_listeners method")
def _handle_local_instance_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle local deployment completed messages sent from the
hosting OS. This is need so we can tell the hosting OS
to sync the latest state from PowerVC. Once a deployment
completes in PowerVC the instances go into activating task
@ -1004,11 +1026,10 @@ class PowerVCCloudManager(manager.Manager):
back from spawn thus sending the completed event.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
LOG.debug(_("Handling local notification: %s" %
message.get('event_type')))
payload = message.get('payload')
hosting_id = payload.get('instance_id')
# Attempt to get the local instance.
@ -1016,7 +1037,7 @@ class PowerVCCloudManager(manager.Manager):
try:
instance = db.instance_get_by_uuid(context, hosting_id)
except exception.InstanceNotFound:
LOG.debug(_("Local Instance %s Not Found" % hosting_id))
LOG.debug(_("Local Instance %s Not Found") % hosting_id)
return
# Get the PVC instance
@ -1029,28 +1050,33 @@ class PowerVCCloudManager(manager.Manager):
else:
LOG.debug(_('PowerVC instance could not be found'))
def _handle_local_deferred_host_updates(self, context, message):
"""
Handle live migration completed messages sent from PowerVC.
def _handle_local_deferred_host_updates(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle live migration completed messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
hosting_id = self._pre_process_message(message)
hosting_id = self._pre_process_message(payload)
# Attempt to get the local instance.
instance = None
try:
instance = db.instance_get_by_uuid(context, hosting_id)
except exception.InstanceNotFound:
LOG.debug(_("Local Instance %s Not Found" % hosting_id))
LOG.debug(_("Local Instance %s Not Found") % hosting_id)
return
# See if the instance is deferring host scheduling.
# If it is exit immediately.
if not self.driver._check_defer_placement(instance):
LOG.debug(_("Local Instance %s did not defer scheduling"
% hosting_id))
LOG.debug(_("Local Instance %s did not defer scheduling")
% hosting_id)
return
# Get the PVC instance
@ -1064,19 +1090,24 @@ class PowerVCCloudManager(manager.Manager):
self.driver.update_instance_host(context, instance)
except Exception:
LOG.debug(_('Problem updating local instance host '
'information, instance: %s' % instance['id']))
'information, instance: %s') % instance['id'])
else:
LOG.debug(_('Tried to update instance host value but the'
' instance could not be found in PowerVC'))
def _handle_powervc_instance_create(self, context, message):
"""
Handle instance create messages sent from PowerVC.
def _handle_powervc_instance_create(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle instance create messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
powervc_instance_id = self._pre_process_message(message)
powervc_instance_id = self._pre_process_message(payload)
# Check for matching local instance
matched_instances = self._get_local_instance_by_pvc_id(
@ -1101,20 +1132,25 @@ class PowerVCCloudManager(manager.Manager):
try:
self._add_local_instance(context, instance)
except Exception as e:
LOG.warning(_("Failed to insert instance due to: %s "
% str(e)))
LOG.warning(_("Failed to insert instance due to: %s ")
% str(e))
else:
LOG.debug(_('Tried to add newly created instance but it could not '
'be found in PowerVC'))
def _handle_powervc_instance_delete(self, context, message):
"""
Handle instance delete messages sent from PowerVC.
def _handle_powervc_instance_delete(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle instance delete messages sent from PowerVC.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
powervc_instance_id = self._pre_process_message(message)
powervc_instance_id = self._pre_process_message(payload)
# Check for matching local instance
matched_instances = self._get_local_instance_by_pvc_id(
@ -1128,17 +1164,21 @@ class PowerVCCloudManager(manager.Manager):
# Remove the instance from the local OS
self._remove_local_instance(context, matched_instances[0])
def _handle_powervc_instance_state(self, context, message):
"""
Handle instance state changes sent from PowerVC. This includes
def _handle_powervc_instance_state(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle instance state changes sent from PowerVC. This includes
instance update and all other state changes caused by events like
power on, power off, resize, live migration, and snapshot.
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
powervc_instance_id = self._pre_process_message(message)
event_type = message.get('event_type')
powervc_instance_id = self._pre_process_message(payload)
local_instance = self.\
_get_matched_instance_by_pvc_id(context, powervc_instance_id)
@ -1151,25 +1191,29 @@ class PowerVCCloudManager(manager.Manager):
self._update_state(context, local_instance, powervc_instance,
powervc_instance_id, event_type)
def _handle_volume_attach_or_detach(self, context, message):
"""
Handle out of band volume attach or detach event
def _handle_volume_attach_or_detach(self,
context=None,
ctxt=None,
event_type=None,
payload=None):
"""Handle out of band volume attach or detach event
:param: context The security context
:param: message The AMQP message sent from OpenStack (dictionary)
:param: ctxt message context
:param: event_type message event type
:param: payload The AMQP message sent from OpenStack (dictionary)
"""
powervc_instance_id = self._pre_process_message(message)
powervc_instance_id = self._pre_process_message(payload)
local_instance = self.\
_get_matched_instance_by_pvc_id(context, powervc_instance_id)
if not local_instance:
return
payload = message.get('payload')
powervc_volume_id = payload.get('volume_id')
if powervc_volume_id is None:
LOG.warning(_('no valid volume for powervc instance %s' %
powervc_instance_id))
LOG.warning(_('no valid volume for powervc instance %s') %
powervc_instance_id)
return
vol_id = self.cache_volume.get_by_id(powervc_volume_id)
if vol_id is None:
@ -1190,16 +1234,13 @@ class PowerVCCloudManager(manager.Manager):
self.sync_volume_attachment(context, powervc_instance_id,
local_instance)
def _pre_process_message(self, message):
"""
Logging the event type and return the instance id of the nova server
def _pre_process_message(self, payload):
"""Logging the event type and return the instance id of the nova server
instance in the event
:param: message The AMQP message sent from OpenStack (dictionary)
:param: payload The AMQP message sent from OpenStack (dictionary)
:returns instance id triggering the event
"""
LOG.debug(_("Handling notification: %s" % message.get('event_type')))
payload = message.get('payload')
instance_id = payload.get('instance_id')
return instance_id