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:
parent
098dd97f6a
commit
da5c1590ab
119
ceilometer/dispatcher/http.py
Executable file
119
ceilometer/dispatcher/http.py
Executable 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
|
126
ceilometer/tests/dispatcher/test_http.py
Executable file
126
ceilometer/tests/dispatcher/test_http.py
Executable 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
29
doc/source/install/manual.rst
Normal file → Executable 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
1
setup.cfg
Normal file → Executable 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
|
||||
|
Loading…
Reference in New Issue
Block a user