Add notifications for uploads, updates and deletes
Change-Id: I372f77fe2d1a575f2108c9b8d1f69301c0d5eb5e
This commit is contained in:
parent
78c718a9f5
commit
091aae8a6d
1
Authors
1
Authors
@ -1,4 +1,5 @@
|
|||||||
Andrey Brindeyev <abrindeyev@griddynamics.com>
|
Andrey Brindeyev <abrindeyev@griddynamics.com>
|
||||||
|
Brian Lamar <brian.lamar@rackspace.com>
|
||||||
Brian Waldon <brian.waldon@rackspace.com>
|
Brian Waldon <brian.waldon@rackspace.com>
|
||||||
Christopher MacGown <chris@slicehost.com>
|
Christopher MacGown <chris@slicehost.com>
|
||||||
Cory Wright <corywright@gmail.com>
|
Cory Wright <corywright@gmail.com>
|
||||||
|
@ -331,3 +331,60 @@ Can only be specified in configuration files.
|
|||||||
|
|
||||||
Sets the number of seconds after which SQLAlchemy should reconnect to the
|
Sets the number of seconds after which SQLAlchemy should reconnect to the
|
||||||
datastore if no activity has been made on the connection.
|
datastore if no activity has been made on the connection.
|
||||||
|
|
||||||
|
Configuring Notifications
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Glance can optionally generate notifications to be logged or sent to
|
||||||
|
a RabbitMQ queue. The configuration options are specified in the
|
||||||
|
``glance-api.conf`` config file in the section ``[DEFAULT]``.
|
||||||
|
|
||||||
|
* ``notifier_strategy``
|
||||||
|
|
||||||
|
Optional. Default: ``noop``
|
||||||
|
|
||||||
|
Sets the strategy used for notifications. Options are ``logging``,
|
||||||
|
``rabbit`` and ``noop``.
|
||||||
|
For more information :doc:`Glance notifications <notifications>`
|
||||||
|
|
||||||
|
* ``rabbit_host``
|
||||||
|
|
||||||
|
Optional. Default: ``localhost``
|
||||||
|
|
||||||
|
Host to connect to when using ``rabbit`` strategy.
|
||||||
|
|
||||||
|
* ``rabbit_port``
|
||||||
|
|
||||||
|
Optional. Default: ``5672``
|
||||||
|
|
||||||
|
Port to connect to when using ``rabbit`` strategy.
|
||||||
|
|
||||||
|
* ``rabbit_use_ssl``
|
||||||
|
|
||||||
|
Optional. Default: ``false``
|
||||||
|
|
||||||
|
Boolean to use SSL for connecting when using ``rabbit`` strategy.
|
||||||
|
|
||||||
|
* ``rabbit_userid``
|
||||||
|
|
||||||
|
Optional. Default: ``guest``
|
||||||
|
|
||||||
|
Userid to use for connection when using ``rabbit`` strategy.
|
||||||
|
|
||||||
|
* ``rabbit_password``
|
||||||
|
|
||||||
|
Optional. Default: ``guest``
|
||||||
|
|
||||||
|
Password to use for connection when using ``rabbit`` strategy.
|
||||||
|
|
||||||
|
* ``rabbit_virtual_host``
|
||||||
|
|
||||||
|
Optional. Default: ``/``
|
||||||
|
|
||||||
|
Virtual host to use for connection when using ``rabbit`` strategy.
|
||||||
|
|
||||||
|
* ``rabbit_notification_topic``
|
||||||
|
|
||||||
|
Optional. Default: ``glance_notifications``
|
||||||
|
|
||||||
|
Topic to use for connection when using ``rabbit`` strategy.
|
||||||
|
85
doc/source/notifications.rst
Normal file
85
doc/source/notifications.rst
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
..
|
||||||
|
Copyright 2011 OpenStack, LLC
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Notifications
|
||||||
|
=============
|
||||||
|
|
||||||
|
Notifications can be generated for each upload, update or delete image
|
||||||
|
event. These can be used for auditing, troubleshooting, etc.
|
||||||
|
|
||||||
|
Strategies
|
||||||
|
----------
|
||||||
|
|
||||||
|
* logging
|
||||||
|
|
||||||
|
This strategy uses the standard Python logging infrastructure with
|
||||||
|
the notifications ending up in file specificed by the log_file
|
||||||
|
configuration directive.
|
||||||
|
|
||||||
|
* rabbit
|
||||||
|
|
||||||
|
This strategy sends notifications to a rabbitmq queue. This can then
|
||||||
|
be processed by other services or applications.
|
||||||
|
|
||||||
|
* noop
|
||||||
|
|
||||||
|
This strategy produces no notifications. It is the default strategy.
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------
|
||||||
|
|
||||||
|
Every message contains a handful of attributes.
|
||||||
|
|
||||||
|
* message_id
|
||||||
|
|
||||||
|
UUID identifying the message.
|
||||||
|
|
||||||
|
* publisher_id
|
||||||
|
|
||||||
|
The hostname of the glance instance that generated the message.
|
||||||
|
|
||||||
|
* event_type
|
||||||
|
|
||||||
|
Event that generated the message.
|
||||||
|
|
||||||
|
* priority
|
||||||
|
|
||||||
|
One of WARN, INFO or ERROR.
|
||||||
|
|
||||||
|
* timestamp
|
||||||
|
|
||||||
|
UTC timestamp of when event was generated.
|
||||||
|
|
||||||
|
* payload
|
||||||
|
|
||||||
|
Data specific to the event type.
|
||||||
|
|
||||||
|
Payload
|
||||||
|
-------
|
||||||
|
|
||||||
|
WARN and ERROR events contain a text message in the payload.
|
||||||
|
|
||||||
|
* image.upload
|
||||||
|
|
||||||
|
For INFO events, it is the image metadata.
|
||||||
|
|
||||||
|
* image.update
|
||||||
|
|
||||||
|
For INFO events, it is the image metadata.
|
||||||
|
|
||||||
|
* image.delete
|
||||||
|
|
||||||
|
For INFO events, it is the image id.
|
@ -29,6 +29,22 @@ log_file = /var/log/glance/api.log
|
|||||||
# Send logs to syslog (/dev/log) instead of to file specified by `log_file`
|
# Send logs to syslog (/dev/log) instead of to file specified by `log_file`
|
||||||
use_syslog = False
|
use_syslog = False
|
||||||
|
|
||||||
|
# Notifications can be sent when images are create, updated or deleted.
|
||||||
|
# There are three methods of sending notifications, logging (via the
|
||||||
|
# log_file directive), rabbit (via a rabbitmq queue) or noop (no
|
||||||
|
# notifications sent, the default)
|
||||||
|
notifier_strategy = noop
|
||||||
|
|
||||||
|
# Configuration options if sending notifications via rabbitmq (these are
|
||||||
|
# the defaults)
|
||||||
|
rabbit_host = localhost
|
||||||
|
rabbit_port = 5672
|
||||||
|
rabbit_use_ssl = false
|
||||||
|
rabbit_userid = guest
|
||||||
|
rabbit_password = guest
|
||||||
|
rabbit_virtual_host = /
|
||||||
|
rabbit_notification_topic = glance_notifications
|
||||||
|
|
||||||
# ============ Filesystem Store Options ========================
|
# ============ Filesystem Store Options ========================
|
||||||
|
|
||||||
# Directory that the Filesystem backend store
|
# Directory that the Filesystem backend store
|
||||||
|
@ -34,6 +34,7 @@ from webob.exc import (HTTPNotFound,
|
|||||||
from glance import api
|
from glance import api
|
||||||
from glance import image_cache
|
from glance import image_cache
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
|
from glance.common import notifier
|
||||||
from glance.common import wsgi
|
from glance.common import wsgi
|
||||||
import glance.store
|
import glance.store
|
||||||
import glance.store.filesystem
|
import glance.store.filesystem
|
||||||
@ -79,6 +80,7 @@ class Controller(api.BaseController):
|
|||||||
def __init__(self, options):
|
def __init__(self, options):
|
||||||
self.options = options
|
self.options = options
|
||||||
glance.store.create_stores(options)
|
glance.store.create_stores(options)
|
||||||
|
self.notifier = notifier.Notifier(options)
|
||||||
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""
|
"""
|
||||||
@ -365,6 +367,7 @@ class Controller(api.BaseController):
|
|||||||
image_id,
|
image_id,
|
||||||
{'checksum': checksum,
|
{'checksum': checksum,
|
||||||
'size': size})
|
'size': size})
|
||||||
|
self.notifier.info('image.upload', image_meta)
|
||||||
|
|
||||||
return location
|
return location
|
||||||
|
|
||||||
@ -372,12 +375,14 @@ class Controller(api.BaseController):
|
|||||||
msg = ("Attempt to upload duplicate image: %s") % e
|
msg = ("Attempt to upload duplicate image: %s") % e
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
self._safe_kill(req, image_id)
|
self._safe_kill(req, image_id)
|
||||||
|
self.notifier.error('image.upload', msg)
|
||||||
raise HTTPConflict(msg, request=req)
|
raise HTTPConflict(msg, request=req)
|
||||||
|
|
||||||
except exception.NotAuthorized, e:
|
except exception.NotAuthorized, e:
|
||||||
msg = ("Unauthorized upload attempt: %s") % e
|
msg = ("Unauthorized upload attempt: %s") % e
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
self._safe_kill(req, image_id)
|
self._safe_kill(req, image_id)
|
||||||
|
self.notifier.error('image.upload', msg)
|
||||||
raise HTTPForbidden(msg, request=req,
|
raise HTTPForbidden(msg, request=req,
|
||||||
content_type='text/plain')
|
content_type='text/plain')
|
||||||
|
|
||||||
@ -389,6 +394,8 @@ class Controller(api.BaseController):
|
|||||||
|
|
||||||
msg = ("Error uploading image: (%s): '%s") % (
|
msg = ("Error uploading image: (%s): '%s") % (
|
||||||
e.__class__.__name__, str(e))
|
e.__class__.__name__, str(e))
|
||||||
|
|
||||||
|
self.notifier.error('image.upload', msg)
|
||||||
raise HTTPBadRequest(msg, request=req)
|
raise HTTPBadRequest(msg, request=req)
|
||||||
|
|
||||||
def _activate(self, req, image_id, location):
|
def _activate(self, req, image_id, location):
|
||||||
@ -539,7 +546,10 @@ class Controller(api.BaseController):
|
|||||||
% locals())
|
% locals())
|
||||||
for line in msg.split('\n'):
|
for line in msg.split('\n'):
|
||||||
logger.error(line)
|
logger.error(line)
|
||||||
|
self.notifier.error('image.update', msg)
|
||||||
raise HTTPBadRequest(msg, request=req, content_type="text/plain")
|
raise HTTPBadRequest(msg, request=req, content_type="text/plain")
|
||||||
|
else:
|
||||||
|
self.notifier.info('image.update', image_meta)
|
||||||
|
|
||||||
return {'image_meta': image_meta}
|
return {'image_meta': image_meta}
|
||||||
|
|
||||||
@ -571,6 +581,7 @@ class Controller(api.BaseController):
|
|||||||
schedule_delete_from_backend(image['location'], self.options,
|
schedule_delete_from_backend(image['location'], self.options,
|
||||||
req.context, id)
|
req.context, id)
|
||||||
registry.delete_image_metadata(self.options, req.context, id)
|
registry.delete_image_metadata(self.options, req.context, id)
|
||||||
|
self.notifier.info('image.delete', id)
|
||||||
|
|
||||||
def get_store_or_400(self, request, store_name):
|
def get_store_or_400(self, request, store_name):
|
||||||
"""
|
"""
|
||||||
|
@ -159,3 +159,7 @@ class StoreDeleteNotSupported(GlanceException):
|
|||||||
class StoreAddDisabled(GlanceException):
|
class StoreAddDisabled(GlanceException):
|
||||||
message = _("Configuration for store failed. Adding images to this "
|
message = _("Configuration for store failed. Adding images to this "
|
||||||
"store is disabled.")
|
"store is disabled.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidNotifierStrategy(GlanceException):
|
||||||
|
message = "'%(strategy)s' is not an available notifier strategy."
|
||||||
|
147
glance/common/notifier.py
Normal file
147
glance/common/notifier.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import kombu.connection
|
||||||
|
|
||||||
|
from glance.common import config
|
||||||
|
from glance.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
class NoopStrategy(object):
|
||||||
|
"""A notifier that does nothing when called."""
|
||||||
|
|
||||||
|
def __init__(self, options):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def warn(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def info(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingStrategy(object):
|
||||||
|
"""A notifier that calls logging when called."""
|
||||||
|
|
||||||
|
def __init__(self, options):
|
||||||
|
self.logger = logging.getLogger('glance.notifier.logging_notifier')
|
||||||
|
|
||||||
|
def warn(self, msg):
|
||||||
|
self.logger.warn(msg)
|
||||||
|
|
||||||
|
def info(self, msg):
|
||||||
|
self.logger.info(msg)
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
self.logger.error(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class RabbitStrategy(object):
|
||||||
|
"""A notifier that puts a message on a queue when called."""
|
||||||
|
|
||||||
|
def __init__(self, options):
|
||||||
|
"""Initialize the rabbit notification strategy."""
|
||||||
|
self._options = options
|
||||||
|
host = self._get_option('rabbit_host', 'str', 'localhost')
|
||||||
|
port = self._get_option('rabbit_port', 'int', 5672)
|
||||||
|
use_ssl = self._get_option('rabbit_use_ssl', 'bool', False)
|
||||||
|
userid = self._get_option('rabbit_userid', 'str', 'guest')
|
||||||
|
password = self._get_option('rabbit_password', 'str', 'guest')
|
||||||
|
virtual_host = self._get_option('rabbit_virtual_host', 'str', '/')
|
||||||
|
|
||||||
|
self.connection = kombu.connection.BrokerConnection(
|
||||||
|
hostname=host,
|
||||||
|
userid=userid,
|
||||||
|
password=password,
|
||||||
|
virtual_host=virtual_host,
|
||||||
|
ssl=use_ssl)
|
||||||
|
|
||||||
|
self.topic = self._get_option('rabbit_notification_topic',
|
||||||
|
'str',
|
||||||
|
'glance_notifications')
|
||||||
|
|
||||||
|
def _get_option(self, name, datatype, default):
|
||||||
|
"""Retrieve a configuration option."""
|
||||||
|
return config.get_option(self._options,
|
||||||
|
name,
|
||||||
|
type=datatype,
|
||||||
|
default=default)
|
||||||
|
|
||||||
|
def _send_message(self, message, priority):
|
||||||
|
topic = "%s.%s" % (self.topic, priority)
|
||||||
|
queue = self.connection.SimpleQueue(topic)
|
||||||
|
queue.put(message, serializer="json")
|
||||||
|
queue.close()
|
||||||
|
|
||||||
|
def warn(self, msg):
|
||||||
|
self._send_message(msg, "WARN")
|
||||||
|
|
||||||
|
def info(self, msg):
|
||||||
|
self._send_message(msg, "INFO")
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
self._send_message(msg, "ERROR")
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier(object):
|
||||||
|
"""Uses a notification strategy to send out messages about events."""
|
||||||
|
|
||||||
|
STRATEGIES = {
|
||||||
|
"logging": LoggingStrategy,
|
||||||
|
"rabbit": RabbitStrategy,
|
||||||
|
"noop": NoopStrategy,
|
||||||
|
"default": NoopStrategy,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, options, strategy=None):
|
||||||
|
strategy = config.get_option(options, "notifier_strategy",
|
||||||
|
type="str", default="default")
|
||||||
|
try:
|
||||||
|
self.strategy = self.STRATEGIES[strategy](options)
|
||||||
|
except KeyError:
|
||||||
|
raise exception.InvalidNotifierStrategy(strategy=strategy)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_message(event_type, priority, payload):
|
||||||
|
return {
|
||||||
|
"message_id": str(uuid.uuid4()),
|
||||||
|
"publisher_id": socket.gethostname(),
|
||||||
|
"event_type": event_type,
|
||||||
|
"priority": priority,
|
||||||
|
"payload": payload,
|
||||||
|
"timestamp": str(datetime.datetime.utcnow()),
|
||||||
|
}
|
||||||
|
|
||||||
|
def warn(self, event_type, payload):
|
||||||
|
msg = self.generate_message(event_type, "WARN", payload)
|
||||||
|
self.strategy.warn(msg)
|
||||||
|
|
||||||
|
def info(self, event_type, payload):
|
||||||
|
msg = self.generate_message(event_type, "INFO", payload)
|
||||||
|
self.strategy.info(msg)
|
||||||
|
|
||||||
|
def error(self, event_type, payload):
|
||||||
|
msg = self.generate_message(event_type, "ERROR", payload)
|
||||||
|
self.strategy.error(msg)
|
123
glance/tests/unit/test_notifier.py
Normal file
123
glance/tests/unit/test_notifier.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack, LLC
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from glance.common import exception
|
||||||
|
from glance.common import notifier
|
||||||
|
|
||||||
|
|
||||||
|
class TestInvalidNotifier(unittest.TestCase):
|
||||||
|
"""Test that notifications are generated appropriately"""
|
||||||
|
|
||||||
|
def test_cannot_create(self):
|
||||||
|
options = {"notifier_strategy": "invalid_notifier"}
|
||||||
|
self.assertRaises(exception.InvalidNotifierStrategy,
|
||||||
|
notifier.Notifier,
|
||||||
|
options)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoggingNotifier(unittest.TestCase):
|
||||||
|
"""Test the logging notifier is selected and works properly."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
options = {"notifier_strategy": "logging"}
|
||||||
|
self.called = False
|
||||||
|
self.logger = logging.getLogger("glance.notifier.logging_notifier")
|
||||||
|
self.notifier = notifier.Notifier(options)
|
||||||
|
|
||||||
|
def _called(self, msg):
|
||||||
|
self.called = msg
|
||||||
|
|
||||||
|
def test_warn(self):
|
||||||
|
self.logger.warn = self._called
|
||||||
|
self.notifier.warn("test_event", "test_message")
|
||||||
|
if self.called is False:
|
||||||
|
self.fail("Did not call logging library correctly.")
|
||||||
|
|
||||||
|
def test_info(self):
|
||||||
|
self.logger.info = self._called
|
||||||
|
self.notifier.info("test_event", "test_message")
|
||||||
|
if self.called is False:
|
||||||
|
self.fail("Did not call logging library correctly.")
|
||||||
|
|
||||||
|
def test_erorr(self):
|
||||||
|
self.logger.error = self._called
|
||||||
|
self.notifier.error("test_event", "test_message")
|
||||||
|
if self.called is False:
|
||||||
|
self.fail("Did not call logging library correctly.")
|
||||||
|
|
||||||
|
|
||||||
|
class TestNoopNotifier(unittest.TestCase):
|
||||||
|
"""Test that the noop notifier works...and does nothing?"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
options = {"notifier_strategy": "noop"}
|
||||||
|
self.notifier = notifier.Notifier(options)
|
||||||
|
|
||||||
|
def test_warn(self):
|
||||||
|
self.notifier.warn("test_event", "test_message")
|
||||||
|
|
||||||
|
def test_info(self):
|
||||||
|
self.notifier.info("test_event", "test_message")
|
||||||
|
|
||||||
|
def test_error(self):
|
||||||
|
self.notifier.error("test_event", "test_message")
|
||||||
|
|
||||||
|
|
||||||
|
class TestRabbitNotifier(unittest.TestCase):
|
||||||
|
"""Test AMQP/Rabbit notifier works."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
notifier.RabbitStrategy._send_message = self._send_message
|
||||||
|
self.called = False
|
||||||
|
options = {"notifier_strategy": "rabbit"}
|
||||||
|
self.notifier = notifier.Notifier(options)
|
||||||
|
|
||||||
|
def _send_message(self, message, priority):
|
||||||
|
self.called = {
|
||||||
|
"message": message,
|
||||||
|
"priority": priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_warn(self):
|
||||||
|
self.notifier.warn("test_event", "test_message")
|
||||||
|
|
||||||
|
if self.called is False:
|
||||||
|
self.fail("Did not call _send_message properly.")
|
||||||
|
|
||||||
|
self.assertEquals("test_message", self.called["message"]["payload"])
|
||||||
|
self.assertEquals("WARN", self.called["message"]["priority"])
|
||||||
|
|
||||||
|
def test_info(self):
|
||||||
|
self.notifier.info("test_event", "test_message")
|
||||||
|
|
||||||
|
if self.called is False:
|
||||||
|
self.fail("Did not call _send_message properly.")
|
||||||
|
|
||||||
|
self.assertEquals("test_message", self.called["message"]["payload"])
|
||||||
|
self.assertEquals("INFO", self.called["message"]["priority"])
|
||||||
|
|
||||||
|
def test_error(self):
|
||||||
|
self.notifier.error("test_event", "test_message")
|
||||||
|
|
||||||
|
if self.called is False:
|
||||||
|
self.fail("Did not call _send_message properly.")
|
||||||
|
|
||||||
|
self.assertEquals("test_message", self.called["message"]["payload"])
|
||||||
|
self.assertEquals("ERROR", self.called["message"]["priority"])
|
@ -20,3 +20,4 @@ sqlalchemy-migrate>=0.6,<0.7
|
|||||||
bzr
|
bzr
|
||||||
httplib2
|
httplib2
|
||||||
xattr>=0.6.0
|
xattr>=0.6.0
|
||||||
|
kombu
|
||||||
|
Loading…
Reference in New Issue
Block a user