add http dispatcher

http dispatcher posts meter data to a http target. The dispatcher
can be configure to only sends cadf event if only cadf metering
data is desired.

Change-Id: I714245e49eb6a6538d9a2d5967d17b82cd47547e
implements: blueprint http-dispatcher
This commit is contained in:
Tong Li 2014-12-01 22:54:48 -05:00
parent 098dd97f6a
commit da5c1590ab
4 changed files with 265 additions and 10 deletions

119
ceilometer/dispatcher/http.py Executable file
View File

@ -0,0 +1,119 @@
# 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 json
from oslo.config import cfg
import requests
from ceilometer import dispatcher
from ceilometer.openstack.common.gettextutils import _
from ceilometer.openstack.common import log
from ceilometer.publisher import utils as publisher_utils
LOG = log.getLogger(__name__)
http_dispatcher_opts = [
cfg.StrOpt('target',
default='',
help='The target where the http request will be sent to. '
'If this is not set, no data will be posted. For '
'example: target = http://hostname:1234/path'),
cfg.BoolOpt('cadf_only',
default=False,
help='The flag which indicates if only cadf message should '
'be posted. If false, all meters will be posted.'),
cfg.IntOpt('timeout',
default=5,
help='The max time in second to wait for a request to '
'timeout.'),
]
cfg.CONF.register_opts(http_dispatcher_opts, group="dispatcher_http")
class HttpDispatcher(dispatcher.Base):
"""Dispatcher class for posting metering data into a http target.
To enable this dispatcher, the following option needs to be present in
ceilometer.conf file
dispatchers = http
Dispatcher specific options can be added as follows:
[dispatcher_http]
target = www.example.com
cadf_only = true
timeout = 2
"""
def __init__(self, conf):
super(HttpDispatcher, self).__init__(conf)
self.headers = {'Content-type': 'application/json'}
self.timeout = self.conf.dispatcher_http.timeout
self.target = self.conf.dispatcher_http.target
self.cadf_only = self.conf.dispatcher_http.cadf_only
def record_metering_data(self, data):
if self.target == '':
# if the target was not set, do not do anything
LOG.error(_('Dispatcher target was not set, no meter will '
'be posted. Set the target in the ceilometer.conf '
'file'))
return
# 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 %(counter_name)s '
'for %(resource_id)s @ %(timestamp)s: %(counter_volume)s')
% ({'counter_name': meter['counter_name'],
'resource_id': meter['resource_id'],
'timestamp': meter.get('timestamp', 'NO TIMESTAMP'),
'counter_volume': meter['counter_volume']}))
if publisher_utils.verify_signature(
meter,
self.conf.publisher.metering_secret):
try:
if self.cadf_only:
# Only cadf messages are being wanted.
req_data = meter.get('resource_metadata',
{}).get('request')
if req_data and 'CADF_EVENT' in req_data:
data = req_data['CADF_EVENT']
else:
continue
else:
# Every meter should be posted to the target
data = meter
res = requests.post(self.target,
data=json.dumps(data),
headers=self.headers,
timeout=self.timeout)
LOG.debug(_('Message posting finished with status code '
'%d.') % res.status_code)
except Exception as err:
LOG.exception(_('Failed to record metering data: %s'),
err)
else:
LOG.warning(_(
'message signature invalid, discarding message: %r'),
meter)
def record_events(self, events):
pass

View File

@ -0,0 +1,126 @@
#
# 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 mock
from oslo.config import fixture as fixture_config
from oslotest import base
import requests
from ceilometer.dispatcher import http
from ceilometer.publisher import utils
class TestDispatcherHttp(base.BaseTestCase):
def setUp(self):
super(TestDispatcherHttp, self).setUp()
self.CONF = self.useFixture(fixture_config.Config()).conf
self.msg = {'counter_name': 'test',
'resource_id': self.id(),
'counter_volume': 1,
}
self.msg['message_signature'] = utils.compute_signature(
self.msg,
self.CONF.publisher.metering_secret,
)
def test_http_dispatcher_config_options(self):
self.CONF.dispatcher_http.target = 'fake'
self.CONF.dispatcher_http.timeout = 2
self.CONF.dispatcher_http.cadf_only = True
dispatcher = http.HttpDispatcher(self.CONF)
self.assertEqual('fake', dispatcher.target)
self.assertEqual(2, dispatcher.timeout)
self.assertEqual(True, dispatcher.cadf_only)
def test_http_dispatcher_with_no_target(self):
self.CONF.dispatcher_http.target = ''
dispatcher = http.HttpDispatcher(self.CONF)
# The target should be None
self.assertEqual('', dispatcher.target)
with mock.patch.object(requests, 'post') as post:
dispatcher.record_metering_data(self.msg)
# Since the target is not set, no http post should occur, thus the
# call_count should be zero.
self.assertEqual(0, post.call_count)
def test_http_dispatcher_with_no_metadata(self):
self.CONF.dispatcher_http.target = 'fake'
self.CONF.dispatcher_http.cadf_only = True
dispatcher = http.HttpDispatcher(self.CONF)
with mock.patch.object(requests, 'post') as post:
dispatcher.record_metering_data(self.msg)
self.assertEqual(0, post.call_count)
def test_http_dispatcher_without_cadf_event(self):
self.CONF.dispatcher_http.target = 'fake'
self.CONF.dispatcher_http.cadf_only = True
dispatcher = http.HttpDispatcher(self.CONF)
self.msg['resource_metadata'] = {'request': {'NONE_CADF_EVENT': {
'q1': 'v1', 'q2': 'v2'}, }, }
self.msg['message_signature'] = utils.compute_signature(
self.msg,
self.CONF.publisher.metering_secret,
)
with mock.patch.object(requests, 'post') as post:
dispatcher.record_metering_data(self.msg)
# Since the meter does not have metadata or CADF_EVENT, the method
# call count should be zero
self.assertEqual(0, post.call_count)
def test_http_dispatcher_with_cadf_event(self):
self.CONF.dispatcher_http.target = 'fake'
self.CONF.dispatcher_http.cadf_only = True
dispatcher = http.HttpDispatcher(self.CONF)
self.msg['resource_metadata'] = {'request': {'CADF_EVENT': {
'q1': 'v1', 'q2': 'v2'}, }, }
self.msg['message_signature'] = utils.compute_signature(
self.msg,
self.CONF.publisher.metering_secret,
)
with mock.patch.object(requests, 'post') as post:
dispatcher.record_metering_data(self.msg)
self.assertEqual(1, post.call_count)
def test_http_dispatcher_with_none_cadf_event(self):
self.CONF.dispatcher_http.target = 'fake'
self.CONF.dispatcher_http.cadf_only = False
dispatcher = http.HttpDispatcher(self.CONF)
self.msg['resource_metadata'] = {'any': {'thing1': 'v1',
'thing2': 'v2', }, }
self.msg['message_signature'] = utils.compute_signature(
self.msg,
self.CONF.publisher.metering_secret,
)
with mock.patch.object(requests, 'post') as post:
dispatcher.record_metering_data(self.msg)
self.assertEqual(1, post.call_count)

29
doc/source/install/manual.rst Normal file → Executable file
View File

@ -633,20 +633,29 @@ 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 ships multiple dispatchers currently. They are database, file and
http dispatcher. As the names imply, database dispatcher sends metering data
to a database, file dispatcher logs meters into a file, http dispatcher posts
the meters onto a http target. Each dispatcher can have its own configuration
parameters. Please see available configuration parameters at the beginning of
each dispatcher file.
To check if any of the dispatchers is available in your system, you can
inspect the Ceilometer egg entry_points.txt file, you should normally see text
like the following::
[ceilometer.dispatcher]
file = ceilometer.dispatcher.file:FileDispatcher
database = ceilometer.dispatcher.database:DatabaseDispatcher
file = ceilometer.dispatcher.file:FileDispatcher
http = ceilometer.dispatcher.http:HttpDispatcher
To use both dispatchers on a Ceilometer collector service, add the following
line in file ceilometer.conf::
To configure one or multiple dispatchers for Ceilometer, find the Ceilometer
configuration file ceilometer.conf which is normally located at /etc/ceilometer
directory and make changes accordingly. Your configuration file can be in a
different directory.
To use multiple dispatchers on a Ceilometer collector service, add multiple
dispatcher lines in ceilometer.conf file like the following::
[DEFAULT]
dispatcher=database

1
setup.cfg Normal file → Executable file
View File

@ -302,6 +302,7 @@ console_scripts =
ceilometer.dispatcher =
database = ceilometer.dispatcher.database:DatabaseDispatcher
file = ceilometer.dispatcher.file:FileDispatcher
http = ceilometer.dispatcher.http:HttpDispatcher
network.statistics.drivers =
opendaylight = ceilometer.network.statistics.opendaylight.driver:OpenDayLightDriver