summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ceilometer/alarm/rpc.py3
-rw-r--r--ceilometer/api/controllers/v2.py16
-rw-r--r--ceilometer/collector.py12
-rw-r--r--ceilometer/compute/virt/libvirt/inspector.py3
-rw-r--r--ceilometer/event/endpoint.py7
-rw-r--r--ceilometer/messaging.py23
-rw-r--r--ceilometer/nova_client.py2
-rw-r--r--ceilometer/publisher/utils.py3
-rw-r--r--ceilometer/storage/base.py12
-rw-r--r--ceilometer/storage/impl_hbase.py61
-rw-r--r--ceilometer/storage/impl_sqlalchemy.py9
-rw-r--r--ceilometer/storage/pymongo_base.py10
-rw-r--r--ceilometer/tests/api/v2/test_app.py5
-rw-r--r--ceilometer/tests/api/v2/test_capabilities.py3
-rw-r--r--ceilometer/tests/api/v2/test_list_resources_scenarios.py4
-rw-r--r--ceilometer/tests/api/v2/test_statistics_scenarios.py14
-rw-r--r--ceilometer/tests/db.py161
-rw-r--r--ceilometer/tests/event/test_endpoint.py14
-rw-r--r--ceilometer/tests/storage/test_get_connection.py4
-rw-r--r--ceilometer/tests/storage/test_impl_db2.py7
-rw-r--r--ceilometer/tests/storage/test_impl_hbase.py17
-rw-r--r--ceilometer/tests/storage/test_impl_mongodb.py30
-rw-r--r--ceilometer/tests/storage/test_impl_sqlalchemy.py27
-rw-r--r--ceilometer/tests/storage/test_pymongo_base.py9
-rw-r--r--ceilometer/tests/test_collector.py99
-rw-r--r--ceilometer/utils.py8
-rw-r--r--doc/source/api/index.rst8
-rw-r--r--doc/source/architecture.rst12
-rw-r--r--doc/source/conf.py3
-rw-r--r--doc/source/contributing/index.rst1
-rw-r--r--doc/source/glossary.rst57
-rw-r--r--doc/source/index.rst30
-rw-r--r--doc/source/install/mod_wsgi.rst2
-rw-r--r--doc/source/measurements.rst8
-rw-r--r--doc/source/releasenotes/folsom.rst8
-rw-r--r--doc/source/releasenotes/index.rst8
-rw-r--r--setup.cfg3
-rw-r--r--tox.ini8
38 files changed, 433 insertions, 278 deletions
diff --git a/ceilometer/alarm/rpc.py b/ceilometer/alarm/rpc.py
index 7bd6a0af..4e177d0e 100644
--- a/ceilometer/alarm/rpc.py
+++ b/ceilometer/alarm/rpc.py
@@ -16,6 +16,7 @@
# under the License.
from oslo.config import cfg
+import six
from ceilometer import messaging
from ceilometer.openstack.common import context
@@ -61,7 +62,7 @@ class RPCAlarmNotifier(object):
'alarm_id': alarm.alarm_id,
'previous': previous,
'current': alarm.state,
- 'reason': unicode(reason),
+ 'reason': six.text_type(reason),
'reason_data': reason_data})
diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py
index 84494826..4083edd8 100644
--- a/ceilometer/api/controllers/v2.py
+++ b/ceilometer/api/controllers/v2.py
@@ -582,7 +582,8 @@ def _flatten_metadata(metadata):
# output before: a.b:c=d
# output now: a.b.c=d
# So to keep the first variant just replace all dots except the first
- return dict((k.replace('.', ':').replace(':', '.', 1), unicode(v))
+ return dict((k.replace('.', ':').replace(':', '.', 1),
+ six.text_type(v))
for k, v in utils.recursive_keypairs(metadata,
separator='.')
if type(v) is not set)
@@ -2449,12 +2450,14 @@ def _flatten_capabilities(capabilities):
class Capabilities(_Base):
- """A representation of the API capabilities, usually constrained
- by restrictions imposed by the storage driver.
+ """A representation of the API and storage capabilities, usually
+ constrained by restrictions imposed by the storage driver.
"""
api = {wtypes.text: bool}
"A flattened dictionary of API capabilities"
+ storage = {wtypes.text: bool}
+ "A flattened dictionary of storage capabilities"
@classmethod
def sample(cls):
@@ -2493,7 +2496,8 @@ class Capabilities(_Base):
'history': {'query': {'simple': True,
'complex': True}}},
'events': {'query': {'simple': True}},
- })
+ }),
+ storage=_flatten_capabilities({'production_ready': True}),
)
@@ -2509,7 +2513,9 @@ class CapabilitiesController(rest.RestController):
# variation in API capabilities is effectively determined by
# the lack of strict feature parity across storage drivers
driver_capabilities = pecan.request.storage_conn.get_capabilities()
- return Capabilities(api=_flatten_capabilities(driver_capabilities))
+ driver_perf = pecan.request.storage_conn.get_storage_capabilities()
+ return Capabilities(api=_flatten_capabilities(driver_capabilities),
+ storage=_flatten_capabilities(driver_perf))
class V2Controller(object):
diff --git a/ceilometer/collector.py b/ceilometer/collector.py
index 9c43028a..f9c08ee7 100644
--- a/ceilometer/collector.py
+++ b/ceilometer/collector.py
@@ -47,21 +47,17 @@ LOG = log.getLogger(__name__)
class CollectorService(os_service.Service):
"""Listener for the collector service."""
-
- @staticmethod
- def rpc_enabled():
- # cfg.CONF opt from oslo.messaging.transport
- return cfg.CONF.rpc_backend or cfg.CONF.transport_url
-
def start(self):
"""Bind the UDP socket and handle incoming data."""
# ensure dispatcher is configured before starting other services
self.dispatcher_manager = dispatcher.load_dispatcher_manager()
+ self.rpc_server = None
super(CollectorService, self).start()
+
if cfg.CONF.collector.udp_address:
self.tg.add_thread(self.start_udp)
- if self.rpc_enabled():
+ if messaging.TRANSPORT is not None:
self.rpc_server = messaging.get_rpc_server(
cfg.CONF.publisher_rpc.metering_topic, self)
self.rpc_server.start()
@@ -95,7 +91,7 @@ class CollectorService(os_service.Service):
def stop(self):
self.udp_run = False
- if self.rpc_enabled():
+ if self.rpc_server:
self.rpc_server.stop()
super(CollectorService, self).stop()
diff --git a/ceilometer/compute/virt/libvirt/inspector.py b/ceilometer/compute/virt/libvirt/inspector.py
index 183324ec..16fb6bfb 100644
--- a/ceilometer/compute/virt/libvirt/inspector.py
+++ b/ceilometer/compute/virt/libvirt/inspector.py
@@ -18,6 +18,7 @@
from lxml import etree
from oslo.config import cfg
+import six
from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.openstack.common.gettextutils import _
@@ -82,7 +83,7 @@ class LibvirtInspector(virt_inspector.Inspector):
return self._get_connection().lookupByName(instance_name)
except Exception as ex:
if not libvirt or not isinstance(ex, libvirt.libvirtError):
- raise virt_inspector.InspectorException(unicode(ex))
+ raise virt_inspector.InspectorException(six.text_type(ex))
error_code = ex.get_error_code()
msg = ("Error from libvirt while looking up %(instance_name)s: "
"[Error Code %(error_code)s] "
diff --git a/ceilometer/event/endpoint.py b/ceilometer/event/endpoint.py
index 03e55f74..fa05c8a1 100644
--- a/ceilometer/event/endpoint.py
+++ b/ceilometer/event/endpoint.py
@@ -63,7 +63,12 @@ class EventsNotificationEndpoint(object):
LOG.debug(_('Saving event "%s"'), event.event_type)
problem_events = []
for dispatcher_ext in self.dispatcher_manager:
- problem_events.extend(dispatcher_ext.obj.record_events(event))
+ try:
+ problem_events.extend(
+ dispatcher_ext.obj.record_events(event))
+ except NotImplementedError:
+ LOG.warn(_('Event is not implemented with the storage'
+ ' backend'))
if models.Event.UNKNOWN_PROBLEM in [x[0] for x in problem_events]:
if not cfg.CONF.notification.ack_on_event_error:
return oslo.messaging.NotificationResult.REQUEUE
diff --git a/ceilometer/messaging.py b/ceilometer/messaging.py
index 10aba200..88934606 100644
--- a/ceilometer/messaging.py
+++ b/ceilometer/messaging.py
@@ -62,7 +62,7 @@ class JsonPayloadSerializer(oslo.messaging.NoOpSerializer):
return jsonutils.to_primitive(entity, convert_instances=True)
-def setup(url=None):
+def setup(url=None, optional=False):
"""Initialise the oslo.messaging layer."""
global TRANSPORT, NOTIFIER
@@ -73,9 +73,17 @@ def setup(url=None):
if not TRANSPORT:
oslo.messaging.set_transport_defaults('ceilometer')
- TRANSPORT = oslo.messaging.get_transport(cfg.CONF, url,
- aliases=_ALIASES)
- if not NOTIFIER:
+ try:
+ TRANSPORT = oslo.messaging.get_transport(cfg.CONF, url,
+ aliases=_ALIASES)
+ except oslo.messaging.InvalidTransportURL as e:
+ TRANSPORT = None
+ if not optional or e.url:
+ # NOTE(sileht): oslo.messaging is configured but unloadable
+ # so reraise the exception
+ raise
+
+ if not NOTIFIER and TRANSPORT:
serializer = RequestContextSerializer(JsonPayloadSerializer())
NOTIFIER = oslo.messaging.Notifier(TRANSPORT, serializer=serializer)
@@ -83,10 +91,9 @@ def setup(url=None):
def cleanup():
"""Cleanup the oslo.messaging layer."""
global TRANSPORT, NOTIFIER
- assert TRANSPORT is not None
- assert NOTIFIER is not None
- TRANSPORT.cleanup()
- TRANSPORT = NOTIFIER = None
+ if TRANSPORT:
+ TRANSPORT.cleanup()
+ TRANSPORT = NOTIFIER = None
def get_rpc_server(topic, endpoint):
diff --git a/ceilometer/nova_client.py b/ceilometer/nova_client.py
index 092695e8..83595024 100644
--- a/ceilometer/nova_client.py
+++ b/ceilometer/nova_client.py
@@ -116,4 +116,4 @@ class Client(object):
@logged
def floating_ip_get_all(self):
"""Returns all floating ips."""
- return self.nova_client.floating_ips.list()
+ return self.nova_client.floating_ips.list(all_tenants=True)
diff --git a/ceilometer/publisher/utils.py b/ceilometer/publisher/utils.py
index ef83ef50..85a9b62c 100644
--- a/ceilometer/publisher/utils.py
+++ b/ceilometer/publisher/utils.py
@@ -22,6 +22,7 @@ import hashlib
import hmac
from oslo.config import cfg
+import six
from ceilometer import utils
@@ -57,7 +58,7 @@ def compute_signature(message, secret):
# been part of the original message.
continue
digest_maker.update(name)
- digest_maker.update(unicode(value).encode('utf-8'))
+ digest_maker.update(six.text_type(value).encode('utf-8'))
return digest_maker.hexdigest()
diff --git a/ceilometer/storage/base.py b/ceilometer/storage/base.py
index 038ed4d7..cc828c80 100644
--- a/ceilometer/storage/base.py
+++ b/ceilometer/storage/base.py
@@ -143,6 +143,10 @@ class Connection(object):
'events': {'query': {'simple': False}},
}
+ STORAGE_CAPABILITIES = {
+ 'storage': {'production_ready': False},
+ }
+
def __init__(self, url):
"""Constructor."""
pass
@@ -371,3 +375,11 @@ class Connection(object):
"""Return an dictionary representing the capabilities of each driver.
"""
return cls.CAPABILITIES
+
+ @classmethod
+ def get_storage_capabilities(cls):
+ """Return a dictionary representing the performance capabilities.
+
+ This is needed to evaluate the performance of each driver.
+ """
+ return cls.STORAGE_CAPABILITIES
diff --git a/ceilometer/storage/impl_hbase.py b/ceilometer/storage/impl_hbase.py
index 1e9e5b8b..c6d72226 100644
--- a/ceilometer/storage/impl_hbase.py
+++ b/ceilometer/storage/impl_hbase.py
@@ -54,6 +54,11 @@ AVAILABLE_CAPABILITIES = {
}
+AVAILABLE_STORAGE_CAPABILITIES = {
+ 'storage': {'production_ready': True},
+}
+
+
class Connection(base.Connection):
"""Put the data into a HBase database
@@ -112,6 +117,10 @@ class Connection(base.Connection):
CAPABILITIES = utils.update_nested(base.Connection.CAPABILITIES,
AVAILABLE_CAPABILITIES)
+ STORAGE_CAPABILITIES = utils.update_nested(
+ base.Connection.STORAGE_CAPABILITIES,
+ AVAILABLE_STORAGE_CAPABILITIES,
+ )
_memory_instance = None
RESOURCE_TABLE = "resource"
@@ -333,7 +342,7 @@ class Connection(base.Connection):
start=start_timestamp, start_timestamp_op=start_timestamp_op,
end=end_timestamp, end_timestamp_op=end_timestamp_op,
resource=resource, source=source, metaquery=metaquery)
- q, start_row, stop_row = make_sample_query_from_filter(
+ q, start_row, stop_row, __ = make_sample_query_from_filter(
sample_filter, require_meter=False)
with self.conn_pool.connection() as conn:
meter_table = conn.table(self.METER_TABLE)
@@ -424,17 +433,18 @@ class Connection(base.Connection):
"""
with self.conn_pool.connection() as conn:
meter_table = conn.table(self.METER_TABLE)
-
- q, start, stop = make_sample_query_from_filter(
+ q, start, stop, columns = make_sample_query_from_filter(
sample_filter, require_meter=False)
LOG.debug(_("Query Meter Table: %s") % q)
- gen = meter_table.scan(filter=q, row_start=start, row_stop=stop)
+ gen = meter_table.scan(filter=q, row_start=start, row_stop=stop,
+ columns=columns)
for ignored, meter in gen:
if limit is not None:
if limit == 0:
break
else:
limit -= 1
+
d_meter = deserialize_entry(meter)[0]
d_meter['message']['recorded_at'] = d_meter['recorded_at']
yield models.Sample(**d_meter['message'])
@@ -485,10 +495,14 @@ class Connection(base.Connection):
with self.conn_pool.connection() as conn:
meter_table = conn.table(self.METER_TABLE)
- q, start, stop = make_sample_query_from_filter(sample_filter)
+ q, start, stop, columns = make_sample_query_from_filter(
+ sample_filter)
+ # These fields are used in statistics' calculating
+ columns.extend(['f:timestamp', 'f:counter_volume',
+ 'f:counter_unit'])
meters = map(deserialize_entry, list(meter for (ignored, meter) in
meter_table.scan(filter=q, row_start=start,
- row_stop=stop)))
+ row_stop=stop, columns=columns)))
if sample_filter.start:
start_time = sample_filter.start
@@ -590,7 +604,7 @@ class MTable(object):
if key in columns:
ret[row] = data
rows = ret
- elif filter:
+ if filter:
# TODO(jdanjou): we should really parse this properly,
# but at the moment we are only going to support AND here
filters = filter.split('AND')
@@ -787,6 +801,20 @@ def make_query(metaquery=None, **kwargs):
return res_q
+def _get_meter_columns(metaquery, **kwargs):
+ """Return a list of required columns in meter table to be scanned .
+
+ :param metaquery: optional metaquery dict
+ :param kwargs: key-value pairs to filter on. Key should be a real
+ column name in db
+ """
+ columns = ['f:message', 'f:recorded_at']
+ columns.extend(["f:%s" % k for k, v in kwargs.items() if v])
+ if metaquery:
+ columns.extend(["f:r_%s" % k for k, v in metaquery.items() if v])
+ return columns
+
+
def make_sample_query_from_filter(sample_filter, require_meter=True):
"""Return a query dictionary based on the settings in the filter.
@@ -794,6 +822,7 @@ def make_sample_query_from_filter(sample_filter, require_meter=True):
:param require_meter: If true and the filter does not have a meter,
raise an error.
"""
+
meter = sample_filter.meter
if not meter and require_meter:
raise RuntimeError('Missing required meter specifier')
@@ -803,20 +832,22 @@ def make_sample_query_from_filter(sample_filter, require_meter=True):
end=sample_filter.end, end_op=sample_filter.end_timestamp_op,
some_id=meter)
- q = make_query(metaquery=sample_filter.metaquery,
- user_id=sample_filter.user,
- project_id=sample_filter.project,
- counter_name=meter,
- resource_id=sample_filter.resource,
- source=sample_filter.source,
- message_id=sample_filter.message_id)
+ kwargs = dict(user_id=sample_filter.user,
+ project_id=sample_filter.project,
+ counter_name=meter,
+ resource_id=sample_filter.resource,
+ source=sample_filter.source,
+ message_id=sample_filter.message_id)
+
+ q = make_query(metaquery=sample_filter.metaquery, **kwargs)
if q:
ts_query = (" AND " + ts_query) if ts_query else ""
res_q = q + ts_query if ts_query else q
else:
res_q = ts_query if ts_query else None
- return res_q, start_row, end_row
+ columns = _get_meter_columns(metaquery=sample_filter.metaquery, **kwargs)
+ return res_q, start_row, end_row, columns
def _make_general_rowkey_scan(rts_start=None, rts_end=None, some_id=None):
diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py
index a00be836..308013cc 100644
--- a/ceilometer/storage/impl_sqlalchemy.py
+++ b/ceilometer/storage/impl_sqlalchemy.py
@@ -109,6 +109,11 @@ AVAILABLE_CAPABILITIES = {
}
+AVAILABLE_STORAGE_CAPABILITIES = {
+ 'storage': {'production_ready': True},
+}
+
+
def apply_metaquery_filter(session, query, metaquery):
"""Apply provided metaquery filter to existing query.
@@ -215,6 +220,10 @@ class Connection(base.Connection):
"""
CAPABILITIES = utils.update_nested(base.Connection.CAPABILITIES,
AVAILABLE_CAPABILITIES)
+ STORAGE_CAPABILITIES = utils.update_nested(
+ base.Connection.STORAGE_CAPABILITIES,
+ AVAILABLE_STORAGE_CAPABILITIES,
+ )
def __init__(self, url):
self._engine_facade = sqlalchemy_session.EngineFacade.from_config(
diff --git a/ceilometer/storage/pymongo_base.py b/ceilometer/storage/pymongo_base.py
index 35b7a33f..1dd5f90c 100644
--- a/ceilometer/storage/pymongo_base.py
+++ b/ceilometer/storage/pymongo_base.py
@@ -168,12 +168,22 @@ COMMON_AVAILABLE_CAPABILITIES = {
}
+AVAILABLE_STORAGE_CAPABILITIES = {
+ 'storage': {'production_ready': True},
+}
+
+
class Connection(base.Connection):
"""Base Connection class for MongoDB and DB2 drivers.
"""
CAPABILITIES = utils.update_nested(base.Connection.CAPABILITIES,
COMMON_AVAILABLE_CAPABILITIES)
+ STORAGE_CAPABILITIES = utils.update_nested(
+ base.Connection.STORAGE_CAPABILITIES,
+ AVAILABLE_STORAGE_CAPABILITIES,
+ )
+
def get_meters(self, user=None, project=None, resource=None, source=None,
metaquery=None, pagination=None):
"""Return an iterable of models.Meter instances
diff --git a/ceilometer/tests/api/v2/test_app.py b/ceilometer/tests/api/v2/test_app.py
index 3a722946..ac286b85 100644
--- a/ceilometer/tests/api/v2/test_app.py
+++ b/ceilometer/tests/api/v2/test_app.py
@@ -31,7 +31,6 @@ from ceilometer import service
from ceilometer.tests import api as acl
from ceilometer.tests.api.v2 import FunctionalTest
from ceilometer.tests import base
-from ceilometer.tests import db as tests_db
class TestApp(base.BaseTestCase):
@@ -81,8 +80,6 @@ class TestApp(base.BaseTestCase):
class TestPecanApp(FunctionalTest):
- db_manager = tests_db.MongoDbManager()
-
def test_pecan_extension_guessing_unset(self):
# check Pecan does not assume .jpg is an extension
response = self.app.get(self.PATH_PREFIX + '/meters/meter.jpg')
@@ -91,8 +88,6 @@ class TestPecanApp(FunctionalTest):
class TestApiMiddleware(FunctionalTest):
- db_manager = tests_db.MongoDbManager()
-
no_lang_translated_error = 'No lang translated error'
en_US_translated_error = 'en-US translated error'
diff --git a/ceilometer/tests/api/v2/test_capabilities.py b/ceilometer/tests/api/v2/test_capabilities.py
index e502bea6..d25b3861 100644
--- a/ceilometer/tests/api/v2/test_capabilities.py
+++ b/ceilometer/tests/api/v2/test_capabilities.py
@@ -32,5 +32,8 @@ class TestCapabilitiesController(tests_api.FunctionalTest,
def test_capabilities(self):
data = self.get_json(self.url)
+ # check that capabilities data contains both 'api' and 'storage' fields
self.assertIsNotNone(data)
self.assertNotEqual({}, data)
+ self.assertIn('api', data)
+ self.assertIn('storage', data)
diff --git a/ceilometer/tests/api/v2/test_list_resources_scenarios.py b/ceilometer/tests/api/v2/test_list_resources_scenarios.py
index 5fc584b0..8666866f 100644
--- a/ceilometer/tests/api/v2/test_list_resources_scenarios.py
+++ b/ceilometer/tests/api/v2/test_list_resources_scenarios.py
@@ -21,6 +21,8 @@ import datetime
import json
import logging
+import six
+
from ceilometer.openstack.common import timeutils
from ceilometer.publisher import utils
from ceilometer import sample
@@ -40,7 +42,7 @@ class TestListResources(FunctionalTest,
@staticmethod
def _isotime(timestamp):
# drop TZ specifier
- return unicode(timeutils.isotime(timestamp))[:-1]
+ return six.text_type(timeutils.isotime(timestamp))[:-1]
def _verify_sample_timestamps(self, res, first, last):
self.assertTrue('first_sample_timestamp' in res)
diff --git a/ceilometer/tests/api/v2/test_statistics_scenarios.py b/ceilometer/tests/api/v2/test_statistics_scenarios.py
index 05b50918..fdb2c944 100644
--- a/ceilometer/tests/api/v2/test_statistics_scenarios.py
+++ b/ceilometer/tests/api/v2/test_statistics_scenarios.py
@@ -1213,6 +1213,7 @@ class TestGroupByInstance(FunctionalTest,
u'2013-08-01T14:00:00'])
+@tests_db.run_with('mongodb', 'hbase', 'db2')
class TestGroupBySource(FunctionalTest,
tests_db.MixinTestsWithBackendScenarios):
@@ -1222,12 +1223,6 @@ class TestGroupBySource(FunctionalTest,
# moved to TestGroupByInstance with all the other group by statistics
# tests.
- scenarios = [
- ('mongodb', {'db_manager': tests_db.MongoDbManager()}),
- ('hbase', {'db_manager': tests_db.HBaseManager()}),
- ('db2', {'db_manager': tests_db.DB2Manager()}),
- ]
-
PATH = '/meters/instance/statistics'
def setUp(self):
@@ -1566,6 +1561,7 @@ class TestSelectableAggregates(FunctionalTest,
'Bad aggregate: cardinality.injection_attack')
+@tests_db.run_with('mongodb', 'hbase', 'db2')
class TestUnparameterizedAggregates(FunctionalTest,
tests_db.MixinTestsWithBackendScenarios):
@@ -1577,12 +1573,6 @@ class TestUnparameterizedAggregates(FunctionalTest,
# For hbase & db2, the skip on NotImplementedError logic works
# in the usual way.
- scenarios = [
- ('mongodb', {'db_manager': tests_db.MongoDbManager()}),
- ('hbase', {'db_manager': tests_db.HBaseManager()}),
- ('db2', {'db_manager': tests_db.DB2Manager()}),
- ]
-
PATH = '/meters/instance/statistics'
def setUp(self):
diff --git a/ceilometer/tests/db.py b/ceilometer/tests/db.py
index dfdeafea..2f3c2d27 100644
--- a/ceilometer/tests/db.py
+++ b/ceilometer/tests/db.py
@@ -20,11 +20,13 @@
"""Base classes for API tests."""
import fixtures
import os
+import urlparse
import uuid
import warnings
import six
import testscenarios.testcase
+from testtools import testcase
from ceilometer.openstack.common.fixture import config
import ceilometer.openstack.common.fixture.mockpatch as oslo_mock
@@ -32,32 +34,95 @@ from ceilometer import storage
from ceilometer.tests import base as test_base
-class TestBase(testscenarios.testcase.WithScenarios, test_base.BaseTestCase):
- def setUp(self):
- super(TestBase, self).setUp()
-
- self.useFixture(self.db_manager)
+class MongoDbManager(fixtures.Fixture):
- self.CONF = self.useFixture(config.Config()).conf
+ def __init__(self, url):
+ self._url = url
+ def setUp(self):
+ super(MongoDbManager, self).setUp()
with warnings.catch_warnings():
warnings.filterwarnings(
action='ignore',
message='.*you must provide a username and password.*')
try:
- self.conn = storage.get_connection(self.db_manager.connection)
+ self.connection = storage.get_connection(self.url)
except storage.StorageBadVersion as e:
- self.skipTest(six.text_type(e))
+ raise testcase.TestSkipped(six.text_type(e))
+
+ @property
+ def url(self):
+ return '%(url)s_%(db)s' % {
+ 'url': self._url,
+ 'db': uuid.uuid4().hex
+ }
+
+
+class HBaseManager(fixtures.Fixture):
+ def __init__(self, url):
+ self._url = url
+
+ def setUp(self):
+ super(HBaseManager, self).setUp()
+ self.connection = storage.get_connection(self.url)
+
+ @property
+ def url(self):
+ return '%s?table_prefix=%s' % (
+ self._url,
+ uuid.uuid4().hex
+ )
+
+
+class SQLiteManager(fixtures.Fixture):
+
+ def __init__(self, url):
+ self.url = url
+
+ def setUp(self):
+ super(SQLiteManager, self).setUp()
+ self.connection = storage.get_connection(self.url)
+
+
+class TestBase(testscenarios.testcase.WithScenarios, test_base.BaseTestCase):
+
+ DRIVER_MANAGERS = {
+ 'mongodb': MongoDbManager,
+ 'db2': MongoDbManager,
+ 'sqlite': SQLiteManager,
+ 'hbase': HBaseManager,
+ }
+
+ db_url = 'sqlite://' # NOTE(Alexei_987) Set default db url
+
+ def setUp(self):
+ super(TestBase, self).setUp()
+
+ engine = urlparse.urlparse(self.db_url).scheme
+
+ # NOTE(Alexei_987) Shortcut to skip expensive db setUp
+ test_method = self._get_test_method()
+ if (hasattr(test_method, '_run_with')
+ and engine not in test_method._run_with):
+ raise testcase.TestSkipped(
+ 'Test is not applicable for %s' % engine)
+
+ self.db_manager = self._get_driver_manager(engine)(self.db_url)
+ self.useFixture(self.db_manager)
+
+ self.conn = self.db_manager.connection
self.conn.upgrade()
self.useFixture(oslo_mock.Patch('ceilometer.storage.get_connection',
return_value=self.conn))
+ self.CONF = self.useFixture(config.Config()).conf
self.CONF([], project='ceilometer')
# Set a default location for the pipeline config file so the
# tests work even if ceilometer is not installed globally on
# the system.
+ self.CONF.import_opt('pipeline_cfg_file', 'ceilometer.pipeline')
self.CONF.set_override(
'pipeline_cfg_file',
self.path_get('etc/ceilometer/pipeline.yaml')
@@ -68,64 +133,38 @@ class TestBase(testscenarios.testcase.WithScenarios, test_base.BaseTestCase):
self.conn = None
super(TestBase, self).tearDown()
-
-class MongoDbManager(fixtures.Fixture):
-
- def __init__(self):
- self.url = os.environ.get('CEILOMETER_TEST_MONGODB_URL')
- if not self.url:
- raise RuntimeError(
- "No MongoDB test URL set,"
- "export CEILOMETER_TEST_MONGODB_URL environment variable")
-
- def setUp(self):
- super(MongoDbManager, self).setUp()
- self.connection = '%(url)s_%(db)s' % {
- 'url': self.url,
- 'db': uuid.uuid4().hex
- }
-
-
-class DB2Manager(MongoDbManager):
- def __init__(self):
- self.url = (os.environ.get('CEILOMETER_TEST_DB2_URL') or
- os.environ.get('CEILOMETER_TEST_MONGODB_URL'))
- if not self.url:
- raise RuntimeError(
- "No DB2 test URL set, "
- "export CEILOMETER_TEST_DB2_URL environment variable")
+ def _get_driver_manager(self, engine):
+ manager = self.DRIVER_MANAGERS.get(engine)
+ if not manager:
+ raise ValueError('No manager available for %s' % engine)
+ return manager
+
+
+def run_with(*drivers):
+ """Used to mark tests that are only applicable for certain db driver.
+ Skips test if driver is not available
+ """
+ def decorator(test):
+ if isinstance(test, type) and issubclass(test, TestBase):
+ # Decorate all test methods
+ for attr in dir(test):
+ value = getattr(test, attr)
+ if callable(value) and attr.startswith('test_'):
+ value.__func__._run_with = drivers
else:
- # This is to make sure that the db2 driver is used when
- # CEILOMETER_TEST_DB2_URL was not set
- self.url = self.url.replace('mongodb:', 'db2:', 1)
-
-
-class HBaseManager(fixtures.Fixture):
- def __init__(self):
- self.url = os.environ.get('CEILOMETER_TEST_HBASE_URL')
- if not self.url:
- self.url = 'hbase://__test__'
-
- def setUp(self):
- super(HBaseManager, self).setUp()
- self.connection = '%s?table_prefix=%s' % (
- self.url,
- uuid.uuid4().hex)
-
-
-class SQLiteManager(fixtures.Fixture):
-
- def setUp(self):
- super(SQLiteManager, self).setUp()
- self.connection = 'sqlite://'
+ test._run_with = drivers
+ return test
+ return decorator
@six.add_metaclass(test_base.SkipNotImplementedMeta)
class MixinTestsWithBackendScenarios(object):
scenarios = [
- ('sqlite', {'db_manager': SQLiteManager()}),
- ('mongodb', {'db_manager': MongoDbManager()}),
- ('hbase', {'db_manager': HBaseManager()}),
- ('db2', {'db_manager': DB2Manager()})
+ ('sqlite', {'db_url': 'sqlite://'}),
+ ('mongodb', {'db_url': os.environ.get('CEILOMETER_TEST_MONGODB_URL')}),
+ ('hbase', {'db_url': os.environ.get('CEILOMETER_TEST_HBASE_URL',
+ 'hbase://__test__')}),
+ ('db2', {'db_url': (os.environ.get('CEILOMETER_TEST_DB2_URL') or
+ os.environ.get('CEILOMETER_TEST_MONGODB_URL'))})
]
diff --git a/ceilometer/tests/event/test_endpoint.py b/ceilometer/tests/event/test_endpoint.py
index cd1ad84c..03db8d82 100644
--- a/ceilometer/tests/event/test_endpoint.py
+++ b/ceilometer/tests/event/test_endpoint.py
@@ -18,6 +18,7 @@
import mock
+from oslo.config import cfg
import oslo.messaging
from stevedore import extension
@@ -79,6 +80,10 @@ TEST_NOTICE_PAYLOAD = {
}
+cfg.CONF.import_opt('store_events', 'ceilometer.notification',
+ group='notification')
+
+
class TestEventEndpoint(tests_base.BaseTestCase):
def setUp(self):
@@ -100,6 +105,15 @@ class TestEventEndpoint(tests_base.BaseTestCase):
self.endpoint.event_converter.to_event.return_value = mock.MagicMock(
event_type='test.test')
+ @mock.patch('ceilometer.event.endpoint.LOG')
+ def test_event_not_implemented(self, log):
+ self.mock_dispatcher.record_events.side_effect = NotImplementedError
+ message = {'event_type': "foo", 'message_id': "abc"}
+ ret = self.endpoint.process_notification(message)
+ log.warn.assert_called_once_with(
+ 'Event is not implemented with the storage backend')
+ self.assertEqual(oslo.messaging.NotificationResult.HANDLED, ret)
+
def test_message_to_event(self):
self.endpoint.info(TEST_NOTICE_CTXT, 'compute.vagrant-precise',
'compute.instance.create.end',
diff --git a/ceilometer/tests/storage/test_get_connection.py b/ceilometer/tests/storage/test_get_connection.py
index c74178f5..bf05cf34 100644
--- a/ceilometer/tests/storage/test_get_connection.py
+++ b/ceilometer/tests/storage/test_get_connection.py
@@ -21,6 +21,8 @@ from ceilometer.openstack.common import test
from ceilometer import storage
from ceilometer.storage import impl_log
+import six
+
class EngineTest(test.BaseTestCase):
@@ -32,4 +34,4 @@ class EngineTest(test.BaseTestCase):
try:
storage.get_connection('no-such-engine://localhost')
except RuntimeError as err:
- self.assertIn('no-such-engine', unicode(err))
+ self.assertIn('no-such-engine', six.text_type(err))
diff --git a/ceilometer/tests/storage/test_impl_db2.py b/ceilometer/tests/storage/test_impl_db2.py
index c05fe97a..920ddc27 100644
--- a/ceilometer/tests/storage/test_impl_db2.py
+++ b/ceilometer/tests/storage/test_impl_db2.py
@@ -70,3 +70,10 @@ class CapabilitiesTest(test_base.BaseTestCase):
actual_capabilities = impl_db2.Connection.get_capabilities()
self.assertEqual(expected_capabilities, actual_capabilities)
+
+ def test_storage_capabilities(self):
+ expected_capabilities = {
+ 'storage': {'production_ready': True},
+ }
+ actual_capabilities = impl_db2.Connection.get_storage_capabilities()
+ self.assertEqual(expected_capabilities, actual_capabilities)
diff --git a/ceilometer/tests/storage/test_impl_hbase.py b/ceilometer/tests/storage/test_impl_hbase.py
index 805b19cb..3941c9ae 100644
--- a/ceilometer/tests/storage/test_impl_hbase.py
+++ b/ceilometer/tests/storage/test_impl_hbase.py
@@ -30,14 +30,12 @@ from ceilometer.tests import base as test_base
from ceilometer.tests import db as tests_db
-class HBaseEngineTestBase(tests_db.TestBase):
- db_manager = tests_db.HBaseManager()
-
-
-class ConnectionTest(HBaseEngineTestBase):
+class ConnectionTest(tests_db.TestBase,
+ tests_db.MixinTestsWithBackendScenarios):
+ @tests_db.run_with('hbase')
def test_hbase_connection(self):
- conn = hbase.Connection(self.db_manager.connection)
+ conn = hbase.Connection(self.db_manager.url)
self.assertIsInstance(conn.conn_pool.connection(), hbase.MConnection)
class TestConn(object):
@@ -99,3 +97,10 @@ class CapabilitiesTest(test_base.BaseTestCase):
actual_capabilities = hbase.Connection.get_capabilities()
self.assertEqual(expected_capabilities, actual_capabilities)
+
+ def test_storage_capabilities(self):
+ expected_capabilities = {
+ 'storage': {'production_ready': True},
+ }
+ actual_capabilities = hbase.Connection.get_storage_capabilities()
+ self.assertEqual(expected_capabilities, actual_capabilities)
diff --git a/ceilometer/tests/storage/test_impl_mongodb.py b/ceilometer/tests/storage/test_impl_mongodb.py
index c1995537..5331bb99 100644
--- a/ceilometer/tests/storage/test_impl_mongodb.py
+++ b/ceilometer/tests/storage/test_impl_mongodb.py
@@ -30,17 +30,14 @@ from ceilometer.tests import db as tests_db
from ceilometer.tests.storage import test_storage_scenarios
-class MongoDBEngineTestBase(tests_db.TestBase):
- db_manager = tests_db.MongoDbManager()
-
-
-class MongoDBConnection(MongoDBEngineTestBase):
+@tests_db.run_with('mongodb')
+class MongoDBConnection(tests_db.TestBase):
def test_connection_pooling(self):
- test_conn = impl_mongodb.Connection(self.db_manager.connection)
+ test_conn = impl_mongodb.Connection(self.db_manager.url)
self.assertEqual(self.conn.conn, test_conn.conn)
def test_replica_set(self):
- url = self.db_manager.connection + '?replicaSet=foobar'
+ url = self.db_manager._url + '?replicaSet=foobar'
conn = impl_mongodb.Connection(url)
self.assertTrue(conn.conn)
@@ -55,8 +52,8 @@ class MongoDBConnection(MongoDBEngineTestBase):
self.assertEqual(expect, ret)
-class MongoDBTestMarkerBase(test_storage_scenarios.DBTestBase,
- MongoDBEngineTestBase):
+@tests_db.run_with('mongodb')
+class MongoDBTestMarkerBase(test_storage_scenarios.DBTestBase):
#NOTE(Fengqian): All these three test case are the same for resource
#and meter collection. As to alarm, we will set up in AlarmTestPagination.
def test_get_marker(self):
@@ -84,7 +81,8 @@ class MongoDBTestMarkerBase(test_storage_scenarios.DBTestBase,
self.assertTrue(True)
-class IndexTest(MongoDBEngineTestBase):
+@tests_db.run_with('mongodb')
+class IndexTest(tests_db.TestBase):
def test_meter_ttl_index_absent(self):
# create a fake index and check it is deleted
self.conn.db.meter.ensure_index('foo', name='meter_ttl')
@@ -112,8 +110,8 @@ class IndexTest(MongoDBEngineTestBase):
name='meter_ttl'))
-class AlarmTestPagination(test_storage_scenarios.AlarmTestBase,
- MongoDBEngineTestBase):
+@tests_db.run_with('mongodb')
+class AlarmTestPagination(test_storage_scenarios.AlarmTestBase):
def test_alarm_get_marker(self):
self.add_some_alarms()
marker_pairs = {'name': 'red-alert'}
@@ -185,3 +183,11 @@ class CapabilitiesTest(test_base.BaseTestCase):
actual_capabilities = impl_mongodb.Connection.get_capabilities()
self.assertEqual(expected_capabilities, actual_capabilities)
+
+ def test_storage_capabilities(self):
+ expected_capabilities = {
+ 'storage': {'production_ready': True},
+ }
+ actual_capabilities = impl_mongodb.Connection.\
+ get_storage_capabilities()
+ self.assertEqual(expected_capabilities, actual_capabilities)
diff --git a/ceilometer/tests/storage/test_impl_sqlalchemy.py b/ceilometer/tests/storage/test_impl_sqlalchemy.py
index 28512db9..0353bfd6 100644
--- a/ceilometer/tests/storage/test_impl_sqlalchemy.py
+++ b/ceilometer/tests/storage/test_impl_sqlalchemy.py
@@ -37,20 +37,17 @@ from ceilometer.tests import db as tests_db
from ceilometer.tests.storage import test_storage_scenarios as scenarios
-class EventTestBase(tests_db.TestBase):
- # Note: Do not derive from SQLAlchemyEngineTestBase, since we
- # don't want to automatically inherit all the Meter setup.
- db_manager = tests_db.SQLiteManager()
+@tests_db.run_with('sqlite')
+class CeilometerBaseTest(tests_db.TestBase):
-
-class CeilometerBaseTest(EventTestBase):
def test_ceilometer_base(self):
base = sql_models.CeilometerBase()
base['key'] = 'value'
self.assertEqual('value', base['key'])
-class TraitTypeTest(EventTestBase):
+@tests_db.run_with('sqlite')
+class TraitTypeTest(tests_db.TestBase):
# TraitType is a construct specific to sqlalchemy.
# Not applicable to other drivers.
@@ -82,7 +79,8 @@ class TraitTypeTest(EventTestBase):
self.assertTrue(repr.repr(tt2))
-class EventTypeTest(EventTestBase):
+@tests_db.run_with('sqlite')
+class EventTypeTest(tests_db.TestBase):
# EventType is a construct specific to sqlalchemy
# Not applicable to other drivers.
@@ -107,7 +105,8 @@ class MyException(Exception):
pass
-class EventTest(EventTestBase):
+@tests_db.run_with('sqlite')
+class EventTest(tests_db.TestBase):
def test_string_traits(self):
model = models.Trait("Foo", models.Trait.TEXT_TYPE, "my_text")
trait = self.conn._make_trait(model, None)
@@ -173,10 +172,10 @@ class EventTest(EventTestBase):
self.assertTrue(repr.repr(ev))
+@tests_db.run_with('sqlite')
class RelationshipTest(scenarios.DBTestBase):
# Note: Do not derive from SQLAlchemyEngineTestBase, since we
# don't want to automatically inherit all the Meter setup.
- db_manager = tests_db.SQLiteManager()
@patch.object(timeutils, 'utcnow')
def test_clear_metering_data_meta_tables(self, mock_utcnow):
@@ -237,3 +236,11 @@ class CapabilitiesTest(test_base.BaseTestCase):
actual_capabilities = impl_sqlalchemy.Connection.get_capabilities()
self.assertEqual(expected_capabilities, actual_capabilities)
+
+ def test_storage_capabilities(self):
+ expected_capabilities = {
+ 'storage': {'production_ready': True},
+ }
+ actual_capabilities = impl_sqlalchemy.Connection.\
+ get_storage_capabilities()
+ self.assertEqual(expected_capabilities, actual_capabilities)
diff --git a/ceilometer/tests/storage/test_pymongo_base.py b/ceilometer/tests/storage/test_pymongo_base.py
index 71bdb635..639cdd69 100644
--- a/ceilometer/tests/storage/test_pymongo_base.py
+++ b/ceilometer/tests/storage/test_pymongo_base.py
@@ -19,7 +19,6 @@ import datetime
from mock import call
from mock import patch
import pymongo
-import testscenarios
from ceilometer.openstack.common.gettextutils import _
from ceilometer.publisher import utils
@@ -28,17 +27,11 @@ from ceilometer.storage import pymongo_base
from ceilometer.tests import db as tests_db
from ceilometer.tests.storage import test_storage_scenarios
-load_tests = testscenarios.load_tests_apply_scenarios
-
+@tests_db.run_with('mongodb', 'db2')
class CompatibilityTest(test_storage_scenarios.DBTestBase,
tests_db.MixinTestsWithBackendScenarios):
- scenarios = [
- ('mongodb', {'db_manager': tests_db.MongoDbManager()}),
- ('db2', {'db_manager': tests_db.DB2Manager()}),
- ]
-
def prepare_data(self):
def old_record_metering_data(self, data):
self.db.user.update(
diff --git a/ceilometer/tests/test_collector.py b/ceilometer/tests/test_collector.py
index 3b08aaf5..74b4e344 100644
--- a/ceilometer/tests/test_collector.py
+++ b/ceilometer/tests/test_collector.py
@@ -23,9 +23,11 @@ import oslo.messaging
from stevedore import extension
from ceilometer import collector
+from ceilometer import dispatcher
from ceilometer import messaging
from ceilometer.openstack.common import context
from ceilometer.openstack.common.fixture import config
+from ceilometer.openstack.common.fixture import mockpatch
from ceilometer.openstack.common import timeutils
from ceilometer.publisher import utils
from ceilometer import sample
@@ -40,13 +42,14 @@ class FakeConnection():
class TestCollector(tests_base.BaseTestCase):
def setUp(self):
super(TestCollector, self).setUp()
- messaging.setup('fake://')
- self.addCleanup(messaging.cleanup)
-
self.CONF = self.useFixture(config.Config()).conf
self.CONF.set_override("connection", "log://", group='database')
self.CONF.set_override('metering_secret', 'not-so-secret',
group='publisher')
+ self.useFixture(oslo.messaging.conffixture.ConfFixture(self.CONF))
+ self._setup_messaging('fake://')
+ self.addCleanup(messaging.cleanup)
+
self.counter = sample.Sample(
name='foobar',
type='bad',
@@ -76,15 +79,28 @@ class TestCollector(tests_base.BaseTestCase):
self.srv = collector.CollectorService()
- def _make_test_manager(self, plugin):
- return extension.ExtensionManager.make_test_instance([
- extension.Extension(
- 'test',
- None,
- None,
- plugin,
- ),
+ self.useFixture(mockpatch.PatchObject(
+ self.srv.tg, 'add_thread',
+ side_effect=self._dummy_thread_group_add_thread))
+
+ @staticmethod
+ def _dummy_thread_group_add_thread(method):
+ method()
+
+ def _setup_messaging(self, url):
+ messaging.cleanup()
+ self.CONF.set_override('rpc_backend', '')
+ messaging.setup(url, optional=True)
+
+ def _setup_fake_dispatcher(self):
+ plugin = mock.MagicMock()
+ fake_dispatcher = extension.ExtensionManager.make_test_instance([
+ extension.Extension('test', None, None, plugin,),
])
+ self.useFixture(mockpatch.Patch(
+ 'ceilometer.dispatcher.load_dispatcher_manager',
+ return_value=fake_dispatcher))
+ return plugin
def _make_fake_socket(self, sample):
def recvfrom(size):
@@ -104,16 +120,15 @@ class TestCollector(tests_base.BaseTestCase):
conf.udp_port))
def test_record_metering_data(self):
- mock_dispatcher = mock.MagicMock()
- self.srv.dispatcher_manager = self._make_test_manager(mock_dispatcher)
+ mock_dispatcher = self._setup_fake_dispatcher()
+ self.srv.dispatcher_manager = dispatcher.load_dispatcher_manager()
self.srv.record_metering_data(None, self.counter)
mock_dispatcher.record_metering_data.assert_called_once_with(
data=self.counter)
- def test_udp_receive(self):
- self.CONF.set_override('rpc_backend', '')
- mock_dispatcher = mock.MagicMock()
- self.srv.dispatcher_manager = self._make_test_manager(mock_dispatcher)
+ def test_udp_receive_base(self):
+ self._setup_messaging('')
+ mock_dispatcher = self._setup_fake_dispatcher()
self.counter['source'] = 'mysource'
self.counter['counter_name'] = self.counter['name']
self.counter['counter_volume'] = self.counter['volume']
@@ -121,8 +136,9 @@ class TestCollector(tests_base.BaseTestCase):
self.counter['counter_unit'] = self.counter['unit']
udp_socket = self._make_fake_socket(self.counter)
+
with mock.patch('socket.socket', return_value=udp_socket):
- self.srv.start_udp()
+ self.srv.start()
self._verify_udp_socket(udp_socket)
@@ -130,9 +146,8 @@ class TestCollector(tests_base.BaseTestCase):
self.counter)
def test_udp_receive_storage_error(self):
- self.CONF.set_override('rpc_backend', '')
- mock_dispatcher = mock.MagicMock()
- self.srv.dispatcher_manager = self._make_test_manager(mock_dispatcher)
+ self._setup_messaging('')
+ mock_dispatcher = self._setup_fake_dispatcher()
mock_dispatcher.record_metering_data.side_effect = self._raise_error
self.counter['source'] = 'mysource'
@@ -143,7 +158,7 @@ class TestCollector(tests_base.BaseTestCase):
udp_socket = self._make_fake_socket(self.counter)
with mock.patch('socket.socket', return_value=udp_socket):
- self.srv.start_udp()
+ self.srv.start()
self._verify_udp_socket(udp_socket)
@@ -155,29 +170,22 @@ class TestCollector(tests_base.BaseTestCase):
raise Exception
def test_udp_receive_bad_decoding(self):
- self.CONF.set_override('rpc_backend', '')
+ self._setup_messaging('')
udp_socket = self._make_fake_socket(self.counter)
- with mock.patch('socket.socket', return_value=udp_socket):
- with mock.patch('msgpack.loads', self._raise_error):
- self.srv.start_udp()
+ with contextlib.nested(
+ mock.patch('socket.socket', return_value=udp_socket),
+ mock.patch('msgpack.loads', self._raise_error)):
+ self.srv.start()
self._verify_udp_socket(udp_socket)
- @staticmethod
- def _dummy_thread_group_add_thread(method):
- method()
-
@mock.patch.object(oslo.messaging.MessageHandlingServer, 'start')
@mock.patch.object(collector.CollectorService, 'start_udp')
def test_only_udp(self, udp_start, rpc_start):
- """Check that only UDP is started if rpc_backend is empty."""
- self.CONF.set_override('rpc_backend', '')
+ """Check that only UDP is started if messaging transport is unset."""
+ self._setup_messaging('')
udp_socket = self._make_fake_socket(self.counter)
- with contextlib.nested(
- mock.patch.object(
- self.srv.tg, 'add_thread',
- side_effect=self._dummy_thread_group_add_thread),
- mock.patch('socket.socket', return_value=udp_socket)):
+ with mock.patch('socket.socket', return_value=udp_socket):
self.srv.start()
self.assertEqual(0, rpc_start.call_count)
self.assertEqual(1, udp_start.call_count)
@@ -187,22 +195,17 @@ class TestCollector(tests_base.BaseTestCase):
def test_only_rpc(self, udp_start, rpc_start):
"""Check that only RPC is started if udp_address is empty."""
self.CONF.set_override('udp_address', '', group='collector')
- with mock.patch.object(
- self.srv.tg, 'add_thread',
- side_effect=self._dummy_thread_group_add_thread):
- self.srv.start()
- self.assertEqual(1, rpc_start.call_count)
- self.assertEqual(0, udp_start.call_count)
+ self.srv.start()
+ self.assertEqual(1, rpc_start.call_count)
+ self.assertEqual(0, udp_start.call_count)
def test_udp_receive_valid_encoding(self):
+ self._setup_messaging('')
+ mock_dispatcher = self._setup_fake_dispatcher()
self.data_sent = []
with mock.patch('socket.socket',
return_value=self._make_fake_socket(self.utf8_msg)):
- self.srv.rpc_server = mock.MagicMock()
- mock_dispatcher = mock.MagicMock()
- self.srv.dispatcher_manager = \
- self._make_test_manager(mock_dispatcher)
- self.srv.start_udp()
+ self.srv.start()
self.assertTrue(utils.verify_signature(
mock_dispatcher.method_calls[0][1][0],
"not-so-secret"))
diff --git a/ceilometer/utils.py b/ceilometer/utils.py
index 23fc7f6a..ec0a7a7f 100644
--- a/ceilometer/utils.py
+++ b/ceilometer/utils.py
@@ -24,6 +24,8 @@ import datetime
import decimal
import multiprocessing
+import six
+
from ceilometer.openstack.common import timeutils
from ceilometer.openstack.common import units
@@ -45,9 +47,9 @@ def recursive_keypairs(d, separator=':'):
# to avoid inconsistencies in the message signature computation
# for equivalent payloads modulo ordering
first = lambda i: i[0]
- m = map(lambda x: unicode(dict(sorted(x.items(), key=first))
- if isinstance(x, dict)
- else x).encode('utf-8'),
+ m = map(lambda x: six.text_type(dict(sorted(x.items(), key=first))
+ if isinstance(x, dict)
+ else x).encode('utf-8'),
value)
yield name, list(m)
else:
diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst
deleted file mode 100644
index 57dfc7cc..00000000
--- a/doc/source/api/index.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-======================
-Internal API Reference
-======================
-
-.. toctree::
- :maxdepth: 1
-
- autoindex
diff --git a/doc/source/architecture.rst b/doc/source/architecture.rst
index e3075878..7e32679e 100644
--- a/doc/source/architecture.rst
+++ b/doc/source/architecture.rst
@@ -47,15 +47,9 @@ Metering
If you divide a billing process into a 3 step process, as is commonly done in
the telco industry, the steps are:
-1. :term:`Metering` is the process of collecting information about what,
- who, when and how much regarding anything that can be billed. The result of
- this is a collection of "tickets" (a.k.a. samples) which are ready to be
- processed in any way you want.
-2. :term:`Rating` is the process of analysing a series of tickets,
- according to business rules defined by marketing, in order to transform
- them into bill line items with a currency value.
-3. :term:`Billing` is the process to assemble bill line items into a
- single per customer bill, emitting the bill to start the payment collection.
+1. :term:`Metering`
+2. :term:`Rating`
+3. :term:`Billing`
Ceilometer's initial goal was, and still is, strictly limited to step
one. This is a choice made from the beginning not to go into rating or billing,
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 895be3a6..509eb8e7 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -142,7 +142,6 @@ write_autodoc_index()
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
- 'sphinx.ext.todo',
'sphinxcontrib.autohttp.flask',
'wsmeext.sphinxext',
'sphinx.ext.coverage',
@@ -251,7 +250,7 @@ html_theme_options = {
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+#html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
diff --git a/doc/source/contributing/index.rst b/doc/source/contributing/index.rst
index 44df84ea..d570eaaf 100644
--- a/doc/source/contributing/index.rst
+++ b/doc/source/contributing/index.rst
@@ -24,4 +24,3 @@
areas
source
plugins
- ../api/index
diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst
index 0da263ff..20025bf1 100644
--- a/doc/source/glossary.rst
+++ b/doc/source/glossary.rst
@@ -30,6 +30,15 @@
API server
HTTP REST API service for ceilometer.
+ billing
+ Billing is the process to assemble bill line items into a single
+ per customer bill, emitting the bill to start the payment collection.
+
+ bus listener agent
+ Bus listener agent which takes events generated on the Oslo
+ notification bus and transforms them into Ceilometer samples. This
+ is the preferred method of data collection.
+
ceilometer
From Wikipedia [#]_:
@@ -55,21 +64,37 @@
data store
Storage system for recording data collected by ceilometer.
+ http callback
+ HTTP callback is used for calling a predefined URL, whenever an
+ alarm has been set off. The payload of the request contains
+ all the details of why the alarm was triggered.
+
+ log
+ Logging is one of the alarm actions that is useful mostly for debugging,
+ it stores the alarms in a log file.
+
meter
The measurements tracked for a resource. For example, an instance has
a number of meters, such as duration of instance, CPU time used,
number of disk io requests, etc.
Three types of meters are defined in ceilometer:
- * Cumulative: Increasing over time (e.g. disk I/O)
- * Gauge: Discrete items (e.g. floating IPs, image uploads) and fluctuating
- values (e.g. number of Swift objects)
- * Delta: Incremental change to a counter over time (e.g. bandwidth delta)
+
+ * Cumulative: Increasing over time (e.g. disk I/O)
+ * Gauge: Discrete items (e.g. floating IPs, image uploads) and fluctuating
+ values (e.g. number of Swift objects)
+ * Delta: Incremental change to a counter over time (e.g. bandwidth delta)
+
+ metering
+ Metering is the process of collecting information about what,
+ who, when and how much regarding anything that can be billed. The result of
+ this is a collection of "tickets" (a.k.a. samples) which are ready to be
+ processed in any way you want.
notification
- A message sent via an external OpenStack system (e.g Nova, Glance,
- etc) using the Oslo notification mechanism [#]_. These notifications
- are usually sent to and received by Ceilometer through the notifier
- RPC driver.
+ A message sent via an external OpenStack system (e.g Nova, Glance,
+ etc) using the Oslo notification mechanism [#]_. These notifications
+ are usually sent to and received by Ceilometer through the notifier
+ RPC driver.
non-repudiable
From Wikipedia [#]_:
@@ -84,6 +109,22 @@
project
The OpenStack tenant or project.
+ polling agents
+ The polling agent is collecting measurements by polling some API or other
+ tool at a regular interval.
+
+ push agents
+ The push agent is the only solution to fetch data within projects,
+ which do not expose the required data in a remotely usable way. This
+ is not the preferred method as it makes deployment a bit more
+ complex having to add a component to each of the nodes that need
+ to be monitored.
+
+ rating
+ Rating is the process of analysing a series of tickets,
+ according to business rules defined by marketing, in order to transform
+ them into bill line items with a currency value.
+
resource
The OpenStack entity being metered (e.g. instance, volume, image, etc).
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 8740fd95..8bf6a431 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -17,7 +17,7 @@
Welcome to the Ceilometer developer documentation!
==================================================
-The :term:`ceilometer` project aims to deliver a unique point of
+The :term:`Ceilometer` project aims to deliver a unique point of
contact for billing systems to acquire all of the measurements they
need to establish customer billing, across all current OpenStack core
components with work underway to support future OpenStack components.
@@ -37,7 +37,7 @@ What is the purpose of the project and vision for it?
users through a REST API.
* The metering messages are signed and :term:`non-repudiable`.
-This documentation offers information on how ceilometer works and how to
+This documentation offers information on how Ceilometer works and how to
contribute to the project.
Table of contents
@@ -55,26 +55,7 @@ Table of contents
contributing/index
releasenotes/index
glossary
-
-.. - installation
-.. - devstack
-.. - take content from "initial setup"
-.. - configuration
-.. - talk about copying nova config file
-.. - running the services
-.. - agent on compute node
-.. - collector on controller (non-compute) node
-.. - contributing
-.. - joining the project (take this from another set of docs?)
-.. - reporting bugs
-.. - running tests
-.. - submitting patches for review
-.. - writing plugins
-.. - general plugin-based architecture information
-.. - reference to setuptools entry points docs
-.. - pollsters
-.. - notifications
-.. - database engine
+ sourcecode/autoindex
.. update index
@@ -84,8 +65,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
-To Do
-=====
-
-.. todolist::
diff --git a/doc/source/install/mod_wsgi.rst b/doc/source/install/mod_wsgi.rst
index cbbaaf05..e7597931 100644
--- a/doc/source/install/mod_wsgi.rst
+++ b/doc/source/install/mod_wsgi.rst
@@ -43,7 +43,7 @@ work with a copy of ceilometer installed via devstack.
conflicts with Horizon's default configuration.
3. Modify the ``WSGIDaemonProcess`` directive to set the
- ``user`` and ``group`` values to a user available on your server.
+ ``user`` and ``group`` values to a user available on your server.
4. Modify the ``APACHE_RUN_USER`` and ``APACHE_RUN_GROUP`` values to
the name of a user and group available on your server.
diff --git a/doc/source/measurements.rst b/doc/source/measurements.rst
index 7e3a80b0..ac1aa495 100644
--- a/doc/source/measurements.rst
+++ b/doc/source/measurements.rst
@@ -114,11 +114,9 @@ or complete the existing ones.
The meters below are related to the host machine.
-.. note::
-
-By default, Nova will not collect the following meters related to the host
-compute node machine. Nova option 'compute_monitors = ComputeDriverCPUMonitor'
-should be set in nova.conf to enable meters.
+.. note:: By default, Nova will not collect the following meters related to the host
+ compute node machine. Nova option 'compute_monitors = ComputeDriverCPUMonitor'
+ should be set in nova.conf to enable meters.
=============================== ========== ========= ======== ============ ========================
Name Type Unit Resource Origin Note
diff --git a/doc/source/releasenotes/folsom.rst b/doc/source/releasenotes/folsom.rst
index ad18be55..eeaa5869 100644
--- a/doc/source/releasenotes/folsom.rst
+++ b/doc/source/releasenotes/folsom.rst
@@ -13,12 +13,14 @@
License for the specific language governing permissions and limitations
under the License.
+.. _folsom:
+
====================
-Version 0.1 (Folsom)
+Folsom
====================
-This is the first release of ceilometer. Please take all appropriate caution
-in using it, as it is a technology preview at this time.
+This is the first release (Version 0.1) of Ceilometer. Please take all appropriate
+caution in using it, as it is a technology preview at this time.
Version of OpenStack
It is curently tested to work with OpenStack 2012.2 Folsom. Due to its use of
diff --git a/doc/source/releasenotes/index.rst b/doc/source/releasenotes/index.rst
index 72f44571..6d250f6c 100644
--- a/doc/source/releasenotes/index.rst
+++ b/doc/source/releasenotes/index.rst
@@ -18,5 +18,13 @@
============================
.. toctree::
+ :hidden:
folsom
+
+* :ref:`folsom`
+* `Havana`_
+* `Icehouse`_
+
+.. _Havana: https://wiki.openstack.org/wiki/ReleaseNotes/Havana#OpenStack_Metering_.28Ceilometer.29
+.. _IceHouse: https://wiki.openstack.org/wiki/ReleaseNotes/Icehouse#Ceilometer
diff --git a/setup.cfg b/setup.cfg
index 10576a5c..8d379861 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -220,6 +220,9 @@ all_files = 1
build-dir = doc/build
source-dir = doc/source
+[pbr]
+warnerrors = true
+
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
diff --git a/tox.ini b/tox.ini
index fbb64896..59489b9a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,7 +6,7 @@ envlist = py26,py27,py33,pep8
[testenv]
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
-install_command = pip install -U --allow-external pytidylib --allow-insecure pytidylib --allow-external netifaces --allow-insecure netifaces {opts} {packages}
+install_command = pip install -i http://pypi.openstack.org/openstack -U --allow-external pytidylib --allow-insecure pytidylib --allow-external netifaces --allow-insecure netifaces {opts} {packages}
usedevelop = True
setenv = VIRTUAL_ENV={envdir}
EVENTLET_NO_GREENDNS=yes
@@ -33,14 +33,10 @@ commands =
bash tools/config/generate_sample.sh -b . -p ceilometer -o etc/ceilometer
[testenv:docs]
-deps = -r{toxinidir}/requirements.txt
- -r{toxinidir}/test-requirements.txt
commands = python setup.py build_sphinx
[testenv:venv]
-deps = -r{toxinidir}/requirements.txt
- -r{toxinidir}/test-requirements.txt
-commands = bash -x {toxinidir}/setup-test-env.sh {posargs}
+commands = {posargs}
[flake8]
ignore = None