Multiple dispatcher enablement
Ceilometer does not allow multiple dispatchers to be configured. With this implementation, a deployment can be configured to have multiple dispatchers to direct the meters to database and other outlet. blueprint multi-dispatcher-enablement Change-Id: I68c731f65d198d4fa1220f75752f242e74355dfe
This commit is contained in:
parent
962c6a9533
commit
70226205fd
31
ceilometer/collector/dispatcher/__init__.py
Normal file
31
ceilometer/collector/dispatcher/__init__.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2013 IBM
|
||||||
|
#
|
||||||
|
# Author: Tong Li <litong01@us.ibm.com>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
class Base(object):
|
||||||
|
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def record_metering_data(self, context, data):
|
||||||
|
"""Recording metering data interface."""
|
72
ceilometer/collector/dispatcher/database.py
Normal file
72
ceilometer/collector/dispatcher/database.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2013 IBM Corp
|
||||||
|
#
|
||||||
|
# Author: Tong Li <litong01@us.ibm.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from ceilometer import storage
|
||||||
|
from ceilometer.collector import dispatcher
|
||||||
|
from ceilometer.openstack.common import log
|
||||||
|
from ceilometer.openstack.common import timeutils
|
||||||
|
from ceilometer.publisher import rpc as publisher_rpc
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseDispatcher(dispatcher.Base):
|
||||||
|
'''Dispatcher class for recording metering data into database.
|
||||||
|
|
||||||
|
The dispatcher class which records each meter into a database configured
|
||||||
|
in ceilometer configuration file.
|
||||||
|
|
||||||
|
To enable this dispatcher, the following section needs to be present in
|
||||||
|
ceilometer.conf file
|
||||||
|
|
||||||
|
dispatchers = database
|
||||||
|
'''
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(DatabaseDispatcher, self).__init__(conf)
|
||||||
|
self.storage_conn = storage.get_connection(conf)
|
||||||
|
|
||||||
|
def record_metering_data(self, context, data):
|
||||||
|
# We may have receive only one counter on the wire
|
||||||
|
if not isinstance(data, list):
|
||||||
|
data = [data]
|
||||||
|
|
||||||
|
for meter in data:
|
||||||
|
LOG.debug('metering data %s for %s @ %s: %s',
|
||||||
|
meter['counter_name'],
|
||||||
|
meter['resource_id'],
|
||||||
|
meter.get('timestamp', 'NO TIMESTAMP'),
|
||||||
|
meter['counter_volume'])
|
||||||
|
if publisher_rpc.verify_signature(
|
||||||
|
meter,
|
||||||
|
self.conf.publisher_rpc.metering_secret):
|
||||||
|
try:
|
||||||
|
# Convert the timestamp to a datetime instance.
|
||||||
|
# Storage engines are responsible for converting
|
||||||
|
# that value to something they can store.
|
||||||
|
if meter.get('timestamp'):
|
||||||
|
ts = timeutils.parse_isotime(meter['timestamp'])
|
||||||
|
meter['timestamp'] = timeutils.normalize_time(ts)
|
||||||
|
self.storage_conn.record_metering_data(meter)
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error('Failed to record metering data: %s', err)
|
||||||
|
LOG.exception(err)
|
||||||
|
else:
|
||||||
|
LOG.warning(
|
||||||
|
'message signature invalid, discarding message: %r',
|
||||||
|
meter)
|
81
ceilometer/collector/dispatcher/file.py
Normal file
81
ceilometer/collector/dispatcher/file.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2013 IBM Corp
|
||||||
|
#
|
||||||
|
# Author: Tong Li <litong01@us.ibm.com>
|
||||||
|
#
|
||||||
|
# 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 logging.handlers
|
||||||
|
from oslo.config import cfg
|
||||||
|
from ceilometer.collector import dispatcher
|
||||||
|
|
||||||
|
file_dispatcher_opts = [
|
||||||
|
cfg.StrOpt('file_path',
|
||||||
|
default=None,
|
||||||
|
help='Name and the location of the file to record '
|
||||||
|
'meters.'),
|
||||||
|
cfg.IntOpt('max_bytes',
|
||||||
|
default=0,
|
||||||
|
help='The max size of the file'),
|
||||||
|
cfg.IntOpt('backup_count',
|
||||||
|
default=0,
|
||||||
|
help='The max number of the files to keep'),
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(file_dispatcher_opts, group="dispatcher_file")
|
||||||
|
|
||||||
|
|
||||||
|
class FileDispatcher(dispatcher.Base):
|
||||||
|
'''Dispatcher class for recording metering data to a file.
|
||||||
|
|
||||||
|
The dispatcher class which logs each meter into a file configured in
|
||||||
|
ceilometer configuration file. An example configuration may look like the
|
||||||
|
following:
|
||||||
|
|
||||||
|
[dispatcher_file]
|
||||||
|
file_path = /tmp/meters
|
||||||
|
|
||||||
|
To enable this dispatcher, the following section needs to be present in
|
||||||
|
ceilometer.conf file
|
||||||
|
|
||||||
|
[collector]
|
||||||
|
dispatchers = file
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(FileDispatcher, self).__init__(conf)
|
||||||
|
self.log = None
|
||||||
|
|
||||||
|
# if the directory and path are configured, then log to the file
|
||||||
|
if self.conf.dispatcher_file.file_path:
|
||||||
|
dispatcher_logger = logging.Logger('dispather.file')
|
||||||
|
dispatcher_logger.setLevel(logging.INFO)
|
||||||
|
# create rotating file handler which logs meters
|
||||||
|
rfh = logging.handlers.RotatingFileHandler(
|
||||||
|
self.conf.dispatcher_file.file_path,
|
||||||
|
maxBytes=self.conf.dispatcher_file.max_bytes,
|
||||||
|
backupCount=self.conf.dispatcher_file.backup_count,
|
||||||
|
encoding='utf8')
|
||||||
|
|
||||||
|
rfh.setLevel(logging.INFO)
|
||||||
|
# Only wanted the meters to be saved in the file, not the
|
||||||
|
# project root logger.
|
||||||
|
dispatcher_logger.propagate = False
|
||||||
|
dispatcher_logger.addHandler(rfh)
|
||||||
|
self.log = dispatcher_logger
|
||||||
|
|
||||||
|
def record_metering_data(self, context, data):
|
||||||
|
if self.log:
|
||||||
|
self.log.info(data)
|
@ -20,8 +20,8 @@ import msgpack
|
|||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
import socket
|
import socket
|
||||||
from stevedore import extension
|
from stevedore import extension
|
||||||
|
from stevedore import named
|
||||||
|
|
||||||
from ceilometer.publisher import rpc as publisher_rpc
|
|
||||||
from ceilometer.service import prepare_service
|
from ceilometer.service import prepare_service
|
||||||
from ceilometer.openstack.common import context
|
from ceilometer.openstack.common import context
|
||||||
from ceilometer.openstack.common.gettextutils import _
|
from ceilometer.openstack.common.gettextutils import _
|
||||||
@ -30,7 +30,6 @@ from ceilometer.openstack.common import service as os_service
|
|||||||
from ceilometer.openstack.common.rpc import dispatcher as rpc_dispatcher
|
from ceilometer.openstack.common.rpc import dispatcher as rpc_dispatcher
|
||||||
from ceilometer.openstack.common.rpc import service as rpc_service
|
from ceilometer.openstack.common.rpc import service as rpc_service
|
||||||
|
|
||||||
|
|
||||||
from ceilometer.openstack.common import timeutils
|
from ceilometer.openstack.common import timeutils
|
||||||
from ceilometer import pipeline
|
from ceilometer import pipeline
|
||||||
from ceilometer import storage
|
from ceilometer import storage
|
||||||
@ -51,6 +50,9 @@ OPTS = [
|
|||||||
cfg.BoolOpt('store_events',
|
cfg.BoolOpt('store_events',
|
||||||
default=False,
|
default=False,
|
||||||
help='Save event details'),
|
help='Save event details'),
|
||||||
|
cfg.MultiStrOpt('dispatcher',
|
||||||
|
default=['database'],
|
||||||
|
help='dispatcher to process metering data'),
|
||||||
]
|
]
|
||||||
|
|
||||||
cfg.CONF.register_opts(OPTS, group="collector")
|
cfg.CONF.register_opts(OPTS, group="collector")
|
||||||
@ -108,10 +110,10 @@ def udp_collector():
|
|||||||
class CollectorService(rpc_service.Service):
|
class CollectorService(rpc_service.Service):
|
||||||
|
|
||||||
COLLECTOR_NAMESPACE = 'ceilometer.collector'
|
COLLECTOR_NAMESPACE = 'ceilometer.collector'
|
||||||
|
DISPATCHER_NAMESPACE = 'ceilometer.dispatcher'
|
||||||
|
|
||||||
def __init__(self, host, topic, manager=None):
|
def __init__(self, host, topic, manager=None):
|
||||||
super(CollectorService, self).__init__(host, topic, manager)
|
super(CollectorService, self).__init__(host, topic, manager)
|
||||||
self.storage_conn = storage.get_connection(cfg.CONF)
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
super(CollectorService, self).start()
|
super(CollectorService, self).start()
|
||||||
@ -140,6 +142,18 @@ class CollectorService(rpc_service.Service):
|
|||||||
self.COLLECTOR_NAMESPACE)
|
self.COLLECTOR_NAMESPACE)
|
||||||
self.notification_manager.map(self._setup_subscription)
|
self.notification_manager.map(self._setup_subscription)
|
||||||
|
|
||||||
|
# Load all configured dispatchers
|
||||||
|
self.dispatchers = []
|
||||||
|
for dispatcher in named.NamedExtensionManager(
|
||||||
|
namespace=self.DISPATCHER_NAMESPACE,
|
||||||
|
names=cfg.CONF.collector.dispatcher,
|
||||||
|
invoke_on_load=True,
|
||||||
|
invoke_args=[cfg.CONF]):
|
||||||
|
if dispatcher.obj:
|
||||||
|
self.dispatchers.append(dispatcher.obj)
|
||||||
|
|
||||||
|
LOG.info('dispatchers loaded %s' % str(self.dispatchers))
|
||||||
|
|
||||||
# Set ourselves up as a separate worker for the metering data,
|
# Set ourselves up as a separate worker for the metering data,
|
||||||
# since the default for service is to use create_consumer().
|
# since the default for service is to use create_consumer().
|
||||||
self.conn.create_worker(
|
self.conn.create_worker(
|
||||||
@ -168,6 +182,10 @@ class CollectorService(rpc_service.Service):
|
|||||||
LOG.exception('Could not join consumer pool %s/%s' %
|
LOG.exception('Could not join consumer pool %s/%s' %
|
||||||
(topic, exchange_topic.exchange))
|
(topic, exchange_topic.exchange))
|
||||||
|
|
||||||
|
def record_metering_data(self, context, data):
|
||||||
|
for dispatcher in self.dispatchers:
|
||||||
|
dispatcher.record_metering_data(context, data)
|
||||||
|
|
||||||
def process_notification(self, notification):
|
def process_notification(self, notification):
|
||||||
"""Make a notification processed by an handler."""
|
"""Make a notification processed by an handler."""
|
||||||
LOG.debug('notification %r', notification.get('event_type'))
|
LOG.debug('notification %r', notification.get('event_type'))
|
||||||
@ -236,39 +254,6 @@ class CollectorService(rpc_service.Service):
|
|||||||
# FIXME(dhellmann): Spawn green thread?
|
# FIXME(dhellmann): Spawn green thread?
|
||||||
p(list(handler.process_notification(notification)))
|
p(list(handler.process_notification(notification)))
|
||||||
|
|
||||||
def record_metering_data(self, context, data):
|
|
||||||
"""This method is triggered when metering data is
|
|
||||||
cast from an agent.
|
|
||||||
"""
|
|
||||||
# We may have receive only one counter on the wire
|
|
||||||
if not isinstance(data, list):
|
|
||||||
data = [data]
|
|
||||||
|
|
||||||
for meter in data:
|
|
||||||
LOG.debug('metering data %s for %s @ %s: %s',
|
|
||||||
meter['counter_name'],
|
|
||||||
meter['resource_id'],
|
|
||||||
meter.get('timestamp', 'NO TIMESTAMP'),
|
|
||||||
meter['counter_volume'])
|
|
||||||
if publisher_rpc.verify_signature(
|
|
||||||
meter,
|
|
||||||
cfg.CONF.publisher_rpc.metering_secret):
|
|
||||||
try:
|
|
||||||
# Convert the timestamp to a datetime instance.
|
|
||||||
# Storage engines are responsible for converting
|
|
||||||
# that value to something they can store.
|
|
||||||
if meter.get('timestamp'):
|
|
||||||
ts = timeutils.parse_isotime(meter['timestamp'])
|
|
||||||
meter['timestamp'] = timeutils.normalize_time(ts)
|
|
||||||
self.storage_conn.record_metering_data(meter)
|
|
||||||
except Exception as err:
|
|
||||||
LOG.error('Failed to record metering data: %s', err)
|
|
||||||
LOG.exception(err)
|
|
||||||
else:
|
|
||||||
LOG.warning(
|
|
||||||
'message signature invalid, discarding message: %r',
|
|
||||||
meter)
|
|
||||||
|
|
||||||
|
|
||||||
def collector():
|
def collector():
|
||||||
prepare_service()
|
prepare_service()
|
||||||
|
@ -247,6 +247,7 @@ rpc_conn_pool_size 30 Size of RPC c
|
|||||||
rpc_response_timeout 60 Seconds to wait for a response from call or multicall
|
rpc_response_timeout 60 Seconds to wait for a response from call or multicall
|
||||||
rpc_cast_timeout 30 Seconds to wait before a cast expires (TTL).
|
rpc_cast_timeout 30 Seconds to wait before a cast expires (TTL).
|
||||||
Only supported by impl_zmq.
|
Only supported by impl_zmq.
|
||||||
|
dispatchers database The list of dispatchers to process metering data.
|
||||||
=========================== ==================================== ==============================================================
|
=========================== ==================================== ==============================================================
|
||||||
|
|
||||||
A sample configuration file can be found in `ceilometer.conf.sample`_.
|
A sample configuration file can be found in `ceilometer.conf.sample`_.
|
||||||
|
@ -375,3 +375,46 @@ Configuring keystone to work with API
|
|||||||
default port value for ceilometer API is 8777. If the port value
|
default port value for ceilometer API is 8777. If the port value
|
||||||
has been customized, adjust accordingly.
|
has been customized, adjust accordingly.
|
||||||
|
|
||||||
|
|
||||||
|
Use multiple dispatchers
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
double: installing; multiple dispatchers
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The Ceilometer collector allows multiple dispatchers to be configured so that
|
||||||
|
metering data can be easily sent to multiple internal and external systems.
|
||||||
|
|
||||||
|
Ceilometer by default only saves metering data in a database, to allow
|
||||||
|
Ceilometer to send metering data to other systems in addition to the
|
||||||
|
database, multiple dispatchers can be developed and enabled by modifying
|
||||||
|
Ceilometer configuration file.
|
||||||
|
|
||||||
|
Ceilometer ships two dispatchers currently. One is called database
|
||||||
|
dispatcher, and the other is called file dispatcher. As the names imply,
|
||||||
|
database dispatcher basically sends metering data to a database driver,
|
||||||
|
eventually metering data will be saved in database. File dispatcher sends
|
||||||
|
metering data into a file. The location, name, size of the file can be
|
||||||
|
configured in ceilometer configuration file. These two dispatchers are
|
||||||
|
shipped in the Ceilometer egg and defined in the entry_points as follows:
|
||||||
|
|
||||||
|
[ceilometer.dispatcher]
|
||||||
|
file = ceilometer.collector.dispatcher.file:FileDispatcher
|
||||||
|
database = ceilometer.collector.dispatcher.database:DatabaseDispatcher
|
||||||
|
|
||||||
|
To use both dispatchers on a Ceilometer collector service, add the following
|
||||||
|
line in file ceilometer.conf
|
||||||
|
|
||||||
|
[collector]
|
||||||
|
dispatcher=database
|
||||||
|
dispatcher=file
|
||||||
|
|
||||||
|
If there is no dispatcher present, database dispatcher is used as the
|
||||||
|
default. If in some cases such as traffic tests, no dispatcher is needed,
|
||||||
|
one can configure the line like the following:
|
||||||
|
|
||||||
|
dispatcher=
|
||||||
|
|
||||||
|
With above configuration, no dispatcher is used by the Ceilometer collector
|
||||||
|
service, all metering data received by Ceilometer collector will be dropped.
|
||||||
|
@ -597,6 +597,23 @@
|
|||||||
#os_endpoint_type=publicURL
|
#os_endpoint_type=publicURL
|
||||||
|
|
||||||
|
|
||||||
|
[dispatcher_file]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Options defined in ceilometer.collector.dispatcher.file
|
||||||
|
#
|
||||||
|
|
||||||
|
# Name and the location of the file to record meters. (string
|
||||||
|
# value)
|
||||||
|
#file_path=<None>
|
||||||
|
|
||||||
|
# The max size of the file (integer value)
|
||||||
|
#max_bytes=0
|
||||||
|
|
||||||
|
# The max number of the files to keep (integer value)
|
||||||
|
#backup_count=0
|
||||||
|
|
||||||
|
|
||||||
[collector]
|
[collector]
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -617,6 +634,9 @@
|
|||||||
# Save event details (boolean value)
|
# Save event details (boolean value)
|
||||||
#store_events=false
|
#store_events=false
|
||||||
|
|
||||||
|
# dispatcher to process metering data (multi valued)
|
||||||
|
#dispatcher=database
|
||||||
|
|
||||||
|
|
||||||
[matchmaker_ring]
|
[matchmaker_ring]
|
||||||
|
|
||||||
@ -644,4 +664,4 @@
|
|||||||
#password=<None>
|
#password=<None>
|
||||||
|
|
||||||
|
|
||||||
# Total option count: 123
|
# Total option count: 127
|
||||||
|
@ -119,6 +119,10 @@ console_scripts =
|
|||||||
ceilometer-alarm-singleton = ceilometer.alarm.service:singleton_alarm
|
ceilometer-alarm-singleton = ceilometer.alarm.service:singleton_alarm
|
||||||
ceilometer-alarm-notifier = ceilometer.alarm.service:alarm_notifier
|
ceilometer-alarm-notifier = ceilometer.alarm.service:alarm_notifier
|
||||||
|
|
||||||
|
ceilometer.dispatcher =
|
||||||
|
database = ceilometer.collector.dispatcher.database:DatabaseDispatcher
|
||||||
|
file = ceilometer.collector.dispatcher.file:FileDispatcher
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
all_files = 1
|
all_files = 1
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
|
0
tests/collector/dispatcher/__init__.py
Normal file
0
tests/collector/dispatcher/__init__.py
Normal file
114
tests/collector/dispatcher/test_db.py
Normal file
114
tests/collector/dispatcher/test_db.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2013 IBM Corp
|
||||||
|
#
|
||||||
|
# Author: Tong Li <litong01@us.ibm.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""Tests for ceilometer/collector/dispatcher/database.py
|
||||||
|
"""
|
||||||
|
from oslo.config import cfg
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from ceilometer.collector.dispatcher import database
|
||||||
|
from ceilometer.publisher import rpc
|
||||||
|
from ceilometer.tests import base as tests_base
|
||||||
|
from ceilometer.storage import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestDispatcherDB(tests_base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDispatcherDB, self).setUp()
|
||||||
|
self.dispatcher = database.DatabaseDispatcher(cfg.CONF)
|
||||||
|
self.ctx = None
|
||||||
|
|
||||||
|
def test_valid_message(self):
|
||||||
|
msg = {'counter_name': 'test',
|
||||||
|
'resource_id': self.id(),
|
||||||
|
'counter_volume': 1,
|
||||||
|
}
|
||||||
|
msg['message_signature'] = rpc.compute_signature(
|
||||||
|
msg,
|
||||||
|
cfg.CONF.publisher_rpc.metering_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.dispatcher.storage_conn = self.mox.CreateMock(base.Connection)
|
||||||
|
self.dispatcher.storage_conn.record_metering_data(msg)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
self.dispatcher.record_metering_data(self.ctx, msg)
|
||||||
|
self.mox.VerifyAll()
|
||||||
|
|
||||||
|
def test_invalid_message(self):
|
||||||
|
msg = {'counter_name': 'test',
|
||||||
|
'resource_id': self.id(),
|
||||||
|
'counter_volume': 1,
|
||||||
|
}
|
||||||
|
msg['message_signature'] = 'invalid-signature'
|
||||||
|
|
||||||
|
class ErrorConnection:
|
||||||
|
|
||||||
|
called = False
|
||||||
|
|
||||||
|
def record_metering_data(self, data):
|
||||||
|
self.called = True
|
||||||
|
|
||||||
|
self.dispatcher.storage_conn = ErrorConnection()
|
||||||
|
|
||||||
|
self.dispatcher.record_metering_data(self.ctx, msg)
|
||||||
|
|
||||||
|
assert not self.dispatcher.storage_conn.called, \
|
||||||
|
'Should not have called the storage connection'
|
||||||
|
|
||||||
|
def test_timestamp_conversion(self):
|
||||||
|
msg = {'counter_name': 'test',
|
||||||
|
'resource_id': self.id(),
|
||||||
|
'counter_volume': 1,
|
||||||
|
'timestamp': '2012-07-02T13:53:40Z',
|
||||||
|
}
|
||||||
|
msg['message_signature'] = rpc.compute_signature(
|
||||||
|
msg,
|
||||||
|
cfg.CONF.publisher_rpc.metering_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = {}
|
||||||
|
expected.update(msg)
|
||||||
|
expected['timestamp'] = datetime(2012, 7, 2, 13, 53, 40)
|
||||||
|
|
||||||
|
self.dispatcher.storage_conn = self.mox.CreateMock(base.Connection)
|
||||||
|
self.dispatcher.storage_conn.record_metering_data(expected)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
self.dispatcher.record_metering_data(self.ctx, msg)
|
||||||
|
|
||||||
|
def test_timestamp_tzinfo_conversion(self):
|
||||||
|
msg = {'counter_name': 'test',
|
||||||
|
'resource_id': self.id(),
|
||||||
|
'counter_volume': 1,
|
||||||
|
'timestamp': '2012-09-30T15:31:50.262-08:00',
|
||||||
|
}
|
||||||
|
msg['message_signature'] = rpc.compute_signature(
|
||||||
|
msg,
|
||||||
|
cfg.CONF.publisher_rpc.metering_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = {}
|
||||||
|
expected.update(msg)
|
||||||
|
expected['timestamp'] = datetime(2012, 9, 30, 23, 31, 50, 262000)
|
||||||
|
|
||||||
|
self.dispatcher.storage_conn = self.mox.CreateMock(base.Connection)
|
||||||
|
self.dispatcher.storage_conn.record_metering_data(expected)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
self.dispatcher.record_metering_data(self.ctx, msg)
|
105
tests/collector/dispatcher/test_file.py
Normal file
105
tests/collector/dispatcher/test_file.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2013 IBM Corp
|
||||||
|
#
|
||||||
|
# Author: Tong Li <litong01@us.ibm.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""Tests for ceilometer/collector/dispatcher/file.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import logging.handlers
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from ceilometer.collector.dispatcher import file
|
||||||
|
from ceilometer.publisher import rpc
|
||||||
|
from ceilometer.tests import base as tests_base
|
||||||
|
|
||||||
|
|
||||||
|
class TestDispatcherFile(tests_base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDispatcherFile, self).setUp()
|
||||||
|
|
||||||
|
def test_file_dispatcher_with_all_config(self):
|
||||||
|
# Create a temporaryFile to get a file name
|
||||||
|
tf = tempfile.NamedTemporaryFile('r')
|
||||||
|
filename = tf.name
|
||||||
|
tf.close()
|
||||||
|
|
||||||
|
cfg.CONF.dispatcher_file.file_path = filename
|
||||||
|
cfg.CONF.dispatcher_file.max_bytes = 50
|
||||||
|
cfg.CONF.dispatcher_file.backup_count = 5
|
||||||
|
dispatcher = file.FileDispatcher(cfg.CONF)
|
||||||
|
|
||||||
|
# The number of the handlers should be 1
|
||||||
|
self.assertEqual(1, len(dispatcher.log.handlers))
|
||||||
|
# The handler should be RotatingFileHandler
|
||||||
|
handler = dispatcher.log.handlers[0]
|
||||||
|
self.assertTrue(isinstance(handler,
|
||||||
|
logging.handlers.RotatingFileHandler))
|
||||||
|
|
||||||
|
msg = {'counter_name': 'test',
|
||||||
|
'resource_id': self.id(),
|
||||||
|
'counter_volume': 1,
|
||||||
|
}
|
||||||
|
msg['message_signature'] = rpc.compute_signature(
|
||||||
|
msg,
|
||||||
|
cfg.CONF.publisher_rpc.metering_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
# The record_metering_data method should exist and not produce errors.
|
||||||
|
dispatcher.record_metering_data(None, msg)
|
||||||
|
# After the method call above, the file should have been created.
|
||||||
|
self.assertTrue(os.path.exists(handler.baseFilename))
|
||||||
|
|
||||||
|
def test_file_dispatcher_with_path_only(self):
|
||||||
|
# Create a temporaryFile to get a file name
|
||||||
|
tf = tempfile.NamedTemporaryFile('r')
|
||||||
|
filename = tf.name
|
||||||
|
tf.close()
|
||||||
|
|
||||||
|
cfg.CONF.dispatcher_file.file_path = filename
|
||||||
|
cfg.CONF.dispatcher_file.max_bytes = None
|
||||||
|
cfg.CONF.dispatcher_file.backup_count = None
|
||||||
|
dispatcher = file.FileDispatcher(cfg.CONF)
|
||||||
|
|
||||||
|
# The number of the handlers should be 1
|
||||||
|
self.assertEqual(1, len(dispatcher.log.handlers))
|
||||||
|
# The handler should be RotatingFileHandler
|
||||||
|
handler = dispatcher.log.handlers[0]
|
||||||
|
self.assertTrue(isinstance(handler,
|
||||||
|
logging.FileHandler))
|
||||||
|
|
||||||
|
msg = {'counter_name': 'test',
|
||||||
|
'resource_id': self.id(),
|
||||||
|
'counter_volume': 1,
|
||||||
|
}
|
||||||
|
msg['message_signature'] = rpc.compute_signature(
|
||||||
|
msg,
|
||||||
|
cfg.CONF.publisher_rpc.metering_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
# The record_metering_data method should exist and not produce errors.
|
||||||
|
dispatcher.record_metering_data(None, msg)
|
||||||
|
# After the method call above, the file should have been created.
|
||||||
|
self.assertTrue(os.path.exists(handler.baseFilename))
|
||||||
|
|
||||||
|
def test_file_dispatcher_with_no_path(self):
|
||||||
|
cfg.CONF.dispatcher_file.file_path = None
|
||||||
|
dispatcher = file.FileDispatcher(cfg.CONF)
|
||||||
|
|
||||||
|
# The log should be None
|
||||||
|
self.assertIsNone(dispatcher.log)
|
@ -30,7 +30,6 @@ from stevedore.tests import manager as test_manager
|
|||||||
|
|
||||||
from ceilometer import counter
|
from ceilometer import counter
|
||||||
from ceilometer.openstack.common import timeutils
|
from ceilometer.openstack.common import timeutils
|
||||||
from ceilometer.publisher import rpc
|
|
||||||
from ceilometer.collector import service
|
from ceilometer.collector import service
|
||||||
from ceilometer.storage import base
|
from ceilometer.storage import base
|
||||||
from ceilometer.tests import base as tests_base
|
from ceilometer.tests import base as tests_base
|
||||||
@ -184,87 +183,6 @@ class TestCollectorService(TestCollector):
|
|||||||
with patch('ceilometer.openstack.common.rpc.create_connection'):
|
with patch('ceilometer.openstack.common.rpc.create_connection'):
|
||||||
self.srv.start()
|
self.srv.start()
|
||||||
|
|
||||||
def test_valid_message(self):
|
|
||||||
msg = {'counter_name': 'test',
|
|
||||||
'resource_id': self.id(),
|
|
||||||
'counter_volume': 1,
|
|
||||||
}
|
|
||||||
msg['message_signature'] = rpc.compute_signature(
|
|
||||||
msg,
|
|
||||||
cfg.CONF.publisher_rpc.metering_secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.srv.storage_conn = self.mox.CreateMock(base.Connection)
|
|
||||||
self.srv.storage_conn.record_metering_data(msg)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
self.srv.record_metering_data(self.ctx, msg)
|
|
||||||
self.mox.VerifyAll()
|
|
||||||
|
|
||||||
def test_invalid_message(self):
|
|
||||||
msg = {'counter_name': 'test',
|
|
||||||
'resource_id': self.id(),
|
|
||||||
'counter_volume': 1,
|
|
||||||
}
|
|
||||||
msg['message_signature'] = 'invalid-signature'
|
|
||||||
|
|
||||||
class ErrorConnection:
|
|
||||||
|
|
||||||
called = False
|
|
||||||
|
|
||||||
def record_metering_data(self, data):
|
|
||||||
self.called = True
|
|
||||||
|
|
||||||
self.srv.storage_conn = ErrorConnection()
|
|
||||||
|
|
||||||
self.srv.record_metering_data(self.ctx, msg)
|
|
||||||
|
|
||||||
assert not self.srv.storage_conn.called, \
|
|
||||||
'Should not have called the storage connection'
|
|
||||||
|
|
||||||
def test_timestamp_conversion(self):
|
|
||||||
msg = {'counter_name': 'test',
|
|
||||||
'resource_id': self.id(),
|
|
||||||
'counter_volume': 1,
|
|
||||||
'timestamp': '2012-07-02T13:53:40Z',
|
|
||||||
}
|
|
||||||
msg['message_signature'] = rpc.compute_signature(
|
|
||||||
msg,
|
|
||||||
cfg.CONF.publisher_rpc.metering_secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
expected = {}
|
|
||||||
expected.update(msg)
|
|
||||||
expected['timestamp'] = datetime.datetime(2012, 7, 2, 13, 53, 40)
|
|
||||||
|
|
||||||
self.srv.storage_conn = self.mox.CreateMock(base.Connection)
|
|
||||||
self.srv.storage_conn.record_metering_data(expected)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
self.srv.record_metering_data(self.ctx, msg)
|
|
||||||
|
|
||||||
def test_timestamp_tzinfo_conversion(self):
|
|
||||||
msg = {'counter_name': 'test',
|
|
||||||
'resource_id': self.id(),
|
|
||||||
'counter_volume': 1,
|
|
||||||
'timestamp': '2012-09-30T15:31:50.262-08:00',
|
|
||||||
}
|
|
||||||
msg['message_signature'] = rpc.compute_signature(
|
|
||||||
msg,
|
|
||||||
cfg.CONF.publisher_rpc.metering_secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
expected = {}
|
|
||||||
expected.update(msg)
|
|
||||||
expected['timestamp'] = datetime.datetime(2012, 9, 30,
|
|
||||||
23, 31, 50, 262000)
|
|
||||||
|
|
||||||
self.srv.storage_conn = self.mox.CreateMock(base.Connection)
|
|
||||||
self.srv.storage_conn.record_metering_data(expected)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
self.srv.record_metering_data(self.ctx, msg)
|
|
||||||
|
|
||||||
@patch('ceilometer.pipeline.setup_pipeline', MagicMock())
|
@patch('ceilometer.pipeline.setup_pipeline', MagicMock())
|
||||||
def test_process_notification(self):
|
def test_process_notification(self):
|
||||||
# If we try to create a real RPC connection, init_host() never
|
# If we try to create a real RPC connection, init_host() never
|
||||||
|
Loading…
Reference in New Issue
Block a user