oslo.messaging/oslo_messaging/notify/notifier.py
Julien Danjou ff9b4bb938 notify: add a CLI tool to manually send notifications
Change-Id: I01ebd8402e322c5c27dc2fc4c61fb5beb2dddae8
2016-07-13 17:30:56 +02:00

424 lines
15 KiB
Python

# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
# Copyright 2013 Red Hat, 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 abc
import argparse
import logging
import uuid
from debtcollector import renames
from oslo_config import cfg
from oslo_utils import timeutils
import six
from stevedore import extension
from stevedore import named
from oslo_messaging._i18n import _LE
from oslo_messaging import serializer as msg_serializer
from oslo_messaging import transport as msg_transport
_notifier_opts = [
cfg.MultiStrOpt('driver',
default=[],
deprecated_name='notification_driver',
deprecated_group='DEFAULT',
help='The Drivers(s) to handle sending notifications. '
'Possible values are messaging, messagingv2, '
'routing, log, test, noop'),
cfg.StrOpt('transport_url',
deprecated_name='notification_transport_url',
deprecated_group='DEFAULT',
secret=True,
help='A URL representing the messaging driver to use for '
'notifications. If not set, we fall back to the same '
'configuration used for RPC.'),
cfg.ListOpt('topics',
default=['notifications', ],
deprecated_opts=[
cfg.DeprecatedOpt('topics',
group='rpc_notifier2'),
cfg.DeprecatedOpt('notification_topics',
group='DEFAULT')
],
help='AMQP topic used for OpenStack notifications.'),
]
_LOG = logging.getLogger(__name__)
def _send_notification():
"""Command line tool to send notifications manually."""
parser = argparse.ArgumentParser(
description='Oslo.messaging notification sending',
)
parser.add_argument('--config-file',
help='Path to configuration file')
parser.add_argument('--transport-url',
help='Transport URL')
parser.add_argument('--publisher-id',
help='Publisher ID')
parser.add_argument('--event-type',
default="test",
help="Event type")
parser.add_argument('--topic',
nargs='*',
help="Topic to send to")
parser.add_argument('--priority',
default="info",
choices=("info",
"audit",
"warn",
"error",
"critical",
"sample"),
help='Event type')
parser.add_argument('--driver',
default="messagingv2",
choices=extension.ExtensionManager(
'oslo.messaging.notify.drivers'
).names(),
help='Notification driver')
parser.add_argument('payload')
args = parser.parse_args()
conf = cfg.ConfigOpts()
conf([],
default_config_files=[args.config_file] if args.config_file else None)
transport = get_notification_transport(conf, url=args.transport_url)
notifier = Notifier(transport, args.publisher_id, topics=args.topic,
driver=args.driver)
notifier._notify({}, args.event_type, args.payload, args.priority)
@six.add_metaclass(abc.ABCMeta)
class Driver(object):
"""Base driver for Notifications"""
def __init__(self, conf, topics, transport):
"""base driver initialization
:param conf: configuration options
:param topics: list of topics
:param transport: transport driver to use
"""
self.conf = conf
self.topics = topics
self.transport = transport
@abc.abstractmethod
def notify(self, ctxt, msg, priority, retry):
"""send a single notification with a specific priority
:param ctxt: current request context
:param msg: message to be sent
:type msg: str
:param priority: priority of the message
:type priority: str
:param retry: an connection retries configuration
None or -1 means to retry forever
0 means no retry
N means N retries
:type retry: int
"""
pass
def get_notification_transport(conf, url=None,
allowed_remote_exmods=None, aliases=None):
conf.register_opts(_notifier_opts,
group='oslo_messaging_notifications')
if url is None:
url = conf.oslo_messaging_notifications.transport_url
return msg_transport.get_transport(conf, url,
allowed_remote_exmods, aliases)
class Notifier(object):
"""Send notification messages.
The Notifier class is used for sending notification messages over a
messaging transport or other means.
Notification messages follow the following format::
{'message_id': six.text_type(uuid.uuid4()),
'publisher_id': 'compute.host1',
'timestamp': timeutils.utcnow(),
'priority': 'WARN',
'event_type': 'compute.create_instance',
'payload': {'instance_id': 12, ... }}
A Notifier object can be instantiated with a transport object and a
publisher ID:
notifier = messaging.Notifier(get_notification_transport(CONF),
'compute')
and notifications are sent via drivers chosen with the driver
config option and on the topics chosen with the topics config
option in [oslo_messaging_notifications] section.
Alternatively, a Notifier object can be instantiated with a specific
driver or topic::
transport = notifier.get_notification_transport(CONF)
notifier = notifier.Notifier(transport,
'compute.host',
driver='messaging',
topic='notifications')
Notifier objects are relatively expensive to instantiate (mostly the cost
of loading notification drivers), so it is possible to specialize a given
Notifier object with a different publisher id using the prepare() method::
notifier = notifier.prepare(publisher_id='compute')
notifier.info(ctxt, event_type, payload)
"""
@renames.renamed_kwarg('topic', 'topics',
message="Please use topics instead of topic",
version='4.5.0',
removal_version='5.0.0')
def __init__(self, transport, publisher_id=None,
driver=None, topic=None,
serializer=None, retry=None,
topics=None):
"""Construct a Notifier object.
:param transport: the transport to use for sending messages
:type transport: oslo_messaging.Transport
:param publisher_id: field in notifications sent, for example
'compute.host1'
:type publisher_id: str
:param driver: a driver to lookup from oslo_messaging.notify.drivers
:type driver: str
:param topic: the topic which to send messages on
:type topic: str
:param serializer: an optional entity serializer
:type serializer: Serializer
:param retry: an connection retries configuration
None or -1 means to retry forever
0 means no retry
N means N retries
:type retry: int
:param topics: the topics which to send messages on
:type topics: list of strings
"""
conf = transport.conf
conf.register_opts(_notifier_opts,
group='oslo_messaging_notifications')
self.transport = transport
self.publisher_id = publisher_id
self.retry = retry
self._driver_names = ([driver] if driver is not None else
conf.oslo_messaging_notifications.driver)
if topics is not None:
self._topics = topics
elif topic is not None:
self._topics = [topic]
else:
self._topics = conf.oslo_messaging_notifications.topics
self._serializer = serializer or msg_serializer.NoOpSerializer()
self._driver_mgr = named.NamedExtensionManager(
'oslo.messaging.notify.drivers',
names=self._driver_names,
invoke_on_load=True,
invoke_args=[conf],
invoke_kwds={
'topics': self._topics,
'transport': self.transport,
}
)
_marker = object()
def prepare(self, publisher_id=_marker, retry=_marker):
"""Return a specialized Notifier instance.
Returns a new Notifier instance with the supplied publisher_id. Allows
sending notifications from multiple publisher_ids without the overhead
of notification driver loading.
:param publisher_id: field in notifications sent, for example
'compute.host1'
:type publisher_id: str
:param retry: an connection retries configuration
None or -1 means to retry forever
0 means no retry
N means N retries
:type retry: int
"""
return _SubNotifier._prepare(self, publisher_id, retry=retry)
def _notify(self, ctxt, event_type, payload, priority, publisher_id=None,
retry=None):
payload = self._serializer.serialize_entity(ctxt, payload)
ctxt = self._serializer.serialize_context(ctxt)
msg = dict(message_id=six.text_type(uuid.uuid4()),
publisher_id=publisher_id or self.publisher_id,
event_type=event_type,
priority=priority,
payload=payload,
timestamp=six.text_type(timeutils.utcnow()))
def do_notify(ext):
try:
ext.obj.notify(ctxt, msg, priority, retry or self.retry)
except Exception as e:
_LOG.exception(_LE("Problem '%(e)s' attempting to send to "
"notification system. Payload=%(payload)s"),
dict(e=e, payload=payload))
if self._driver_mgr.extensions:
self._driver_mgr.map(do_notify)
def audit(self, ctxt, event_type, payload):
"""Send a notification at audit level.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'AUDIT')
def debug(self, ctxt, event_type, payload):
"""Send a notification at debug level.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'DEBUG')
def info(self, ctxt, event_type, payload):
"""Send a notification at info level.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'INFO')
def warn(self, ctxt, event_type, payload):
"""Send a notification at warning level.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'WARN')
warning = warn
def error(self, ctxt, event_type, payload):
"""Send a notification at error level.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'ERROR')
def critical(self, ctxt, event_type, payload):
"""Send a notification at critical level.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'CRITICAL')
def sample(self, ctxt, event_type, payload):
"""Send 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.
:param ctxt: a request context dict
:type ctxt: dict
:param event_type: describes the event, for example
'compute.create_instance'
:type event_type: str
:param payload: the notification payload
:type payload: dict
:raises: MessageDeliveryFailure
"""
self._notify(ctxt, event_type, payload, 'SAMPLE')
class _SubNotifier(Notifier):
_marker = Notifier._marker
def __init__(self, base, publisher_id, retry):
self._base = base
self.transport = base.transport
self.publisher_id = publisher_id
self.retry = retry
self._serializer = self._base._serializer
self._driver_mgr = self._base._driver_mgr
def _notify(self, ctxt, event_type, payload, priority):
super(_SubNotifier, self)._notify(ctxt, event_type, payload, priority)
@classmethod
def _prepare(cls, base, publisher_id=_marker, retry=_marker):
if publisher_id is cls._marker:
publisher_id = base.publisher_id
if retry is cls._marker:
retry = base.retry
return cls(base, publisher_id, retry=retry)