From 05d6992ad778e802ef3aa1e406dce7d18606c3a1 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 29 Oct 2012 11:41:53 -0400 Subject: [PATCH] Provide a way to disable some plugins Provide configuration options for the three plugin sets users may want to manage (compute pollsters, central pollsters, and notification listeners). Extend the plugin API so the extension manager can ask each plugin if it should be enabled. This allows, for example, the libvirt pollster to be loaded but then recognize that it should not be used and disable itself. Addresses bug #1021350 Change-Id: I82da823845ec49c1a93272411c43073bd4954377 Signed-off-by: Doug Hellmann --- ceilometer/central/manager.py | 15 +++-- ceilometer/collector/manager.py | 21 +++++-- ceilometer/compute/libvirt.py | 102 +++++++++++++++++--------------- ceilometer/compute/manager.py | 16 +++-- ceilometer/extension_manager.py | 71 ++++++++++++++++++++++ ceilometer/plugin.py | 18 +++++- doc/source/configuration.rst | 46 +++++++------- tools/pip-requires | 2 +- 8 files changed, 207 insertions(+), 84 deletions(-) create mode 100644 ceilometer/extension_manager.py diff --git a/ceilometer/central/manager.py b/ceilometer/central/manager.py index 82787c49..ce998e5b 100644 --- a/ceilometer/central/manager.py +++ b/ceilometer/central/manager.py @@ -16,14 +16,21 @@ # License for the specific language governing permissions and limitations # under the License. -from stevedore import extension - from nova import manager +from ceilometer import extension_manager from ceilometer.openstack.common import cfg from ceilometer.openstack.common import log from ceilometer import publish +OPTS = [ + cfg.ListOpt('disabled_central_pollsters', + default=[], + help='list of central pollsters to disable', + ), + ] + +cfg.CONF.register_opts(OPTS) LOG = log.getLogger(__name__) @@ -38,9 +45,9 @@ class AgentManager(manager.Manager): # importable. Need to add check against global # configuration flag and check that asks the plugin if # it should be enabled. - self.ext_manager = extension.ExtensionManager( + self.ext_manager = extension_manager.ActivatedExtensionManager( namespace=PLUGIN_NAMESPACE, - invoke_on_load=True, + disabled_names=cfg.CONF.disabled_central_pollsters, ) return diff --git a/ceilometer/collector/manager.py b/ceilometer/collector/manager.py index a2ebc1db..3aa58126 100644 --- a/ceilometer/collector/manager.py +++ b/ceilometer/collector/manager.py @@ -20,6 +20,7 @@ from stevedore import extension from nova import manager +from ceilometer import extension_manager from ceilometer import meter from ceilometer import publish from ceilometer import storage @@ -36,6 +37,16 @@ except ImportError: import nova.rpc as rpc +OPTS = [ + cfg.ListOpt('disabled_notification_listeners', + default=[], + help='list of listener plugins to disable', + ), + ] + +cfg.CONF.register_opts(OPTS) + + LOG = log.getLogger(__name__) @@ -52,9 +63,10 @@ class CollectorManager(manager.Manager): self.storage_engine = storage.get_engine(cfg.CONF) self.storage_conn = self.storage_engine.get_connection(cfg.CONF) - self.ext_manager = extension.ExtensionManager(self.COLLECTOR_NAMESPACE, - invoke_on_load=True, - ) + self.ext_manager = extension_manager.ActivatedExtensionManager( + namespace=self.COLLECTOR_NAMESPACE, + disabled_names=cfg.CONF.disabled_notification_listeners, + ) if not list(self.ext_manager): LOG.warning('Failed to load any notification handlers for %s', @@ -74,7 +86,8 @@ class CollectorManager(manager.Manager): def _setup_subscription(self, ext, *args, **kwds): handler = ext.obj - LOG.debug('Event types: %r', handler.get_event_types()) + LOG.debug('Event types from %s: %s', + ext.name, ', '.join(handler.get_event_types())) for exchange_topic in handler.get_exchange_topics(cfg.CONF): for topic in exchange_topic.topics: # FIXME(dhellmann): Should be using create_worker(), except diff --git a/ceilometer/compute/libvirt.py b/ceilometer/compute/libvirt.py index 73e4c849..175d020b 100644 --- a/ceilometer/compute/libvirt.py +++ b/ceilometer/compute/libvirt.py @@ -59,7 +59,14 @@ def make_counter_from_instance(instance, name, type, volume): ) -class InstancePollster(plugin.ComputePollster): +class LibVirtPollster(plugin.ComputePollster): + + def is_enabled(self): + # Use a fairly liberal substring check. + return 'libvirt' in FLAGS.compute_driver.lower() + + +class InstancePollster(LibVirtPollster): def get_counters(self, manager, instance): yield make_counter_from_instance(instance, @@ -75,7 +82,7 @@ class InstancePollster(plugin.ComputePollster): ) -class DiskIOPollster(plugin.ComputePollster): +class DiskIOPollster(LibVirtPollster): LOG = log.getLogger(__name__ + '.diskio') @@ -98,53 +105,52 @@ class DiskIOPollster(plugin.ComputePollster): ]) def get_counters(self, manager, instance): - if FLAGS.compute_driver == 'libvirt.LibvirtDriver': - conn = get_libvirt_connection() - # TODO(jd) This does not work see bug#998089 - # for disk in conn.get_disks(instance.name): - try: - disks = self._get_disks(conn, instance.name) - except Exception as err: - self.LOG.warning('Ignoring instance %s: %s', - instance.name, err) - self.LOG.exception(err) - else: - r_bytes = 0 - r_requests = 0 - w_bytes = 0 - w_requests = 0 - for disk in disks: - stats = conn.block_stats(instance.name, disk) - self.LOG.info(self.DISKIO_USAGE_MESSAGE, - instance, disk, stats[0], stats[1], - stats[2], stats[3], stats[4]) - r_bytes += stats[0] - r_requests += stats[1] - w_bytes += stats[3] - w_requests += stats[2] - yield make_counter_from_instance(instance, - name='disk.read.requests', - type=counter.TYPE_CUMULATIVE, - volume=r_requests, - ) - yield make_counter_from_instance(instance, - name='disk.read.bytes', - type=counter.TYPE_CUMULATIVE, - volume=r_bytes, - ) - yield make_counter_from_instance(instance, - name='disk.write.requests', - type=counter.TYPE_CUMULATIVE, - volume=w_requests, - ) - yield make_counter_from_instance(instance, - name='disk.write.bytes', - type=counter.TYPE_CUMULATIVE, - volume=w_bytes, - ) + conn = get_libvirt_connection() + # TODO(jd) This does not work see bug#998089 + # for disk in conn.get_disks(instance.name): + try: + disks = self._get_disks(conn, instance.name) + except Exception as err: + self.LOG.warning('Ignoring instance %s: %s', + instance.name, err) + self.LOG.exception(err) + else: + r_bytes = 0 + r_requests = 0 + w_bytes = 0 + w_requests = 0 + for disk in disks: + stats = conn.block_stats(instance.name, disk) + self.LOG.info(self.DISKIO_USAGE_MESSAGE, + instance, disk, stats[0], stats[1], + stats[2], stats[3], stats[4]) + r_bytes += stats[0] + r_requests += stats[1] + w_bytes += stats[3] + w_requests += stats[2] + yield make_counter_from_instance(instance, + name='disk.read.requests', + type=counter.TYPE_CUMULATIVE, + volume=r_requests, + ) + yield make_counter_from_instance(instance, + name='disk.read.bytes', + type=counter.TYPE_CUMULATIVE, + volume=r_bytes, + ) + yield make_counter_from_instance(instance, + name='disk.write.requests', + type=counter.TYPE_CUMULATIVE, + volume=w_requests, + ) + yield make_counter_from_instance(instance, + name='disk.write.bytes', + type=counter.TYPE_CUMULATIVE, + volume=w_bytes, + ) -class CPUPollster(plugin.ComputePollster): +class CPUPollster(LibVirtPollster): LOG = log.getLogger(__name__ + '.cpu') @@ -166,7 +172,7 @@ class CPUPollster(plugin.ComputePollster): self.LOG.exception(err) -class NetPollster(plugin.ComputePollster): +class NetPollster(LibVirtPollster): LOG = log.getLogger(__name__ + '.net') diff --git a/ceilometer/compute/manager.py b/ceilometer/compute/manager.py index f27dd46b..b0e630c1 100644 --- a/ceilometer/compute/manager.py +++ b/ceilometer/compute/manager.py @@ -16,14 +16,22 @@ # License for the specific language governing permissions and limitations # under the License. -from stevedore import extension - from nova import manager +from ceilometer import extension_manager from ceilometer.openstack.common import cfg from ceilometer.openstack.common import log from ceilometer import publish +OPTS = [ + cfg.ListOpt('disabled_compute_pollsters', + default=[], + help='list of compute agent pollsters to disable', + ), + ] + +cfg.CONF.register_opts(OPTS) + LOG = log.getLogger(__name__) @@ -38,9 +46,9 @@ class AgentManager(manager.Manager): # importable. Need to add check against global # configuration flag and check that asks the plugin if # it should be enabled. - self.ext_manager = extension.ExtensionManager( + self.ext_manager = extension_manager.ActivatedExtensionManager( namespace=PLUGIN_NAMESPACE, - invoke_on_load=True, + disabled_names=cfg.CONF.disabled_compute_pollsters, ) return diff --git a/ceilometer/extension_manager.py b/ceilometer/extension_manager.py new file mode 100644 index 00000000..bc95aae3 --- /dev/null +++ b/ceilometer/extension_manager.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Author: Doug Hellmann +# +# 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. +"""Base class for plugin loader. +""" + +from stevedore import enabled + +from ceilometer.openstack.common import log + + +LOG = log.getLogger(__name__) + + +def should_use_extension(namespace, ext, disabled_names): + """Return boolean indicating whether the extension + should be used. + + Tests the extension against a couple of criteria to see whether it + should be used, logs the reason it is not used if not, and then + returns the result. + """ + if ext.name in disabled_names: + LOG.debug( + '%s extension %r disabled through configuration setting', + namespace, ext.name, + ) + return False + if not ext.obj.is_enabled(): + LOG.debug( + '%s extension %r reported that it is disabled', + namespace, + ext.name, + ) + return False + LOG.debug('using %s extension %r', namespace, ext.name) + return True + + +class ActivatedExtensionManager(enabled.EnabledExtensionManager): + """Loads extensions based on a configurable set that should be + disabled and asking each one if it should be active or not. + """ + + def __init__(self, namespace, disabled_names, invoke_on_load=True, + invoke_args=(), invoke_kwds={}): + + def local_check_func(ext): + return should_use_extension(namespace, ext, disabled_names) + + super(ActivatedExtensionManager, self).__init__( + namespace=namespace, + check_func=local_check_func, + invoke_on_load=invoke_on_load, + invoke_args=invoke_args, + invoke_kwds=invoke_kwds, + ) diff --git a/ceilometer/plugin.py b/ceilometer/plugin.py index 1edf2012..fee54dcc 100644 --- a/ceilometer/plugin.py +++ b/ceilometer/plugin.py @@ -30,11 +30,25 @@ import ceilometer.openstack.common.notifier.rabbit_notifier ExchangeTopics = namedtuple('ExchangeTopics', ['exchange', 'topics']) -class NotificationBase(object): +class PluginBase(object): + """Base class for all plugins. + """ + + def is_enabled(self): + """Return boolean indicating whether this plugin should + be enabled and used by the caller. + """ + return True + + +class NotificationBase(PluginBase): """Base class for plugins that support the notification API.""" __metaclass__ = abc.ABCMeta + def is_enabled(self): + return True + @abc.abstractmethod def get_event_types(self): """Return a sequence of strings defining the event types to be @@ -58,7 +72,7 @@ class NotificationBase(object): return metadata -class PollsterBase(object): +class PollsterBase(PluginBase): """Base class for plugins that support the polling API.""" __metaclass__ = abc.ABCMeta diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 103a699a..d5f1c336 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -23,30 +23,34 @@ Ceilometer specific The following table lists the ceilometer specific options in the global configuration file. Please note that ceilometer uses openstack-common extensively, which requires that the other parameters are set appropriately. For information we are listing the configuration -elements that we use after the ceilometer specific elements. +elements that we use after the ceilometer specific elements. + If you use sql alchemy, its specific paramaters will need to be set. -========================== ==================================== ============================================================== -Parameter Default Note -========================== ==================================== ============================================================== -nova_control_exchange nova Exchange name for Nova notifications -glance_control_exchange glance_notifications Exchange name for Glance notifications -cinder_control_exchange cinder Exchange name for Cinder notifications -quantum_control_exchange quantum Exchange name for Quantum notifications -metering_secret change this or be hacked Secret value for signing metering messages -metering_topic metering the topic ceilometer uses for metering messages -counter_source openstack The source name of emited counters -control_exchange ceilometer AMQP exchange to connect to if using RabbitMQ or Qpid -periodic_interval 600 seconds between running periodic tasks -os-username glance Username to use for openstack service access -os-password admin Password to use for openstack service access -os-tenant-id Tenant ID to use for openstack service access -os-tenant-name admin Tenant name to use for openstack service access -os-auth-url http://localhost:5000/v2.0 Auth URL to use for openstack service access -database_connection mongodb://localhost:27017/ceilometer Database connection string -metering_api_port 8777 The port for the ceilometer API server -========================== ==================================== ============================================================== +=============================== ==================================== ============================================================== +Parameter Default Note +=============================== ==================================== ============================================================== +nova_control_exchange nova Exchange name for Nova notifications +glance_control_exchange glance_notifications Exchange name for Glance notifications +cinder_control_exchange cinder Exchange name for Cinder notifications +quantum_control_exchange quantum Exchange name for Quantum notifications +metering_secret change this or be hacked Secret value for signing metering messages +metering_topic metering the topic ceilometer uses for metering messages +counter_source openstack The source name of emited counters +control_exchange ceilometer AMQP exchange to connect to if using RabbitMQ or Qpid +periodic_interval 600 seconds between running periodic tasks +os-username glance Username to use for openstack service access +os-password admin Password to use for openstack service access +os-tenant-id Tenant ID to use for openstack service access +os-tenant-name admin Tenant name to use for openstack service access +os-auth-url http://localhost:5000/v2.0 Auth URL to use for openstack service access +database_connection mongodb://localhost:27017/ceilometer Database connection string +metering_api_port 8777 The port for the ceilometer API server +disabled_central_pollsters List of central pollsters to skip loading +disabled_compute_pollsters List of compute pollsters to skip loading +disabled_notification_listeners List of notification listeners to skip loading +=============================== ==================================== ============================================================== SQL Alchemy =========== diff --git a/tools/pip-requires b/tools/pip-requires index 38423ade..893e90bc 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -8,6 +8,6 @@ sqlalchemy eventlet anyjson>=0.3.1 Flask==0.9 -stevedore>=0.5 +stevedore>=0.6 python-glanceclient python-cinderclient