diff --git a/MANIFEST.in b/MANIFEST.in index 29782f5b4..16188e036 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,8 @@ include ChangeLog include ceilometer/storage/sqlalchemy/migrate_repo/migrate.cfg exclude .gitignore exclude .gitreview - +recursive-include tests *.py +recursive-include nova_tests *.py global-exclude *.pyc recursive-include public * recursive-include ceilometer/locale * diff --git a/ceilometer/collector/service.py b/ceilometer/collector/service.py index c6237cd31..d6ceb897a 100644 --- a/ceilometer/collector/service.py +++ b/ceilometer/collector/service.py @@ -29,6 +29,10 @@ from ceilometer.openstack.common import log from ceilometer.openstack.common import timeutils from ceilometer.openstack.common.rpc import dispatcher as rpc_dispatcher +# Import rpc_notifier to register `notification_topics` flag so that +# plugins can use it +# FIXME(dhellmann): Use option importing feature of oslo.config instead. +import ceilometer.openstack.common.notifier.rpc_notifier OPTS = [ cfg.ListOpt('disabled_notification_listeners', @@ -56,6 +60,7 @@ class CollectorService(service.PeriodicService): def initialize_service_hook(self, service): '''Consumers must be declared before consume_thread start.''' + LOG.debug('initialize_service_hooks') publisher_manager = dispatch.NameDispatchExtensionManager( namespace=pipeline.PUBLISHER_NAMESPACE, check_func=lambda x: True, @@ -63,6 +68,8 @@ class CollectorService(service.PeriodicService): ) self.pipeline_manager = pipeline.setup_pipeline(publisher_manager) + LOG.debug('loading notification handlers from %s', + self.COLLECTOR_NAMESPACE) self.notification_manager = \ extension_manager.ActivatedExtensionManager( namespace=self.COLLECTOR_NAMESPACE, diff --git a/ceilometer/compute/__init__.py b/ceilometer/compute/__init__.py index e69de29bb..ef4e35586 100644 --- a/ceilometer/compute/__init__.py +++ b/ceilometer/compute/__init__.py @@ -0,0 +1,28 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 New Dream Network, LLC +# +# 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. + +from oslo.config import cfg + +OPTS = [ + cfg.ListOpt('disabled_compute_pollsters', + default=[], + help='list of compute agent pollsters to disable', + ), +] + +cfg.CONF.register_opts(OPTS) diff --git a/ceilometer/compute/instance.py b/ceilometer/compute/instance.py index 728ccc854..dff5ec6e0 100644 --- a/ceilometer/compute/instance.py +++ b/ceilometer/compute/instance.py @@ -46,10 +46,15 @@ def get_metadata_from_object(instance): 'host': instance.hostId, # Image properties 'image_ref': (instance.image['id'] if instance.image else None), - 'image_ref_url': (instance.image['links'][0]['href'] - if instance.image else None), } + # Images that come through the conductor API in the nova notifier + # plugin will not have links. + if instance.image and instance.image.get('links'): + metadata['image_ref_url'] = instance.image['links'][0]['href'] + else: + metadata['image_ref_url'] = None + for name in INSTANCE_PROPERTIES: metadata[name] = getattr(instance, name, u'') return metadata diff --git a/ceilometer/compute/manager.py b/ceilometer/compute/manager.py index f741ff400..cf41dcf7d 100644 --- a/ceilometer/compute/manager.py +++ b/ceilometer/compute/manager.py @@ -17,7 +17,6 @@ # under the License. from oslo.config import cfg -from stevedore import driver from ceilometer import agent from ceilometer import extension_manager @@ -25,18 +24,6 @@ from ceilometer import nova_client from ceilometer.compute.virt import inspector as virt_inspector from ceilometer.openstack.common import log -OPTS = [ - cfg.ListOpt('disabled_compute_pollsters', - default=[], - help='list of compute agent pollsters to disable', - ), - cfg.StrOpt('hypervisor_inspector', - default='libvirt', - help='Inspector to use for inspecting the hypervisor layer'), -] - -cfg.CONF.register_opts(OPTS) - LOG = log.getLogger(__name__) @@ -64,18 +51,6 @@ class PollingTask(agent.PollingTask): self.manager.nv.instance_get_all_by_host(cfg.CONF.host)) -def get_hypervisor_inspector(): - try: - namespace = 'ceilometer.compute.virt' - mgr = driver.DriverManager(namespace, - cfg.CONF.hypervisor_inspector, - invoke_on_load=True) - return mgr.driver - except ImportError as e: - LOG.error("Unable to load the hypervisor inspector: %s" % (e)) - return virt_inspector.Inspector() - - class AgentManager(agent.AgentManager): def __init__(self): @@ -85,7 +60,7 @@ class AgentManager(agent.AgentManager): disabled_names=cfg.CONF.disabled_compute_pollsters, ), ) - self._inspector = get_hypervisor_inspector() + self._inspector = virt_inspector.get_hypervisor_inspector() self.nv = nova_client.Client() def create_polling_task(self): diff --git a/ceilometer/compute/notifications.py b/ceilometer/compute/notifications.py index 904d37025..19c85bee1 100644 --- a/ceilometer/compute/notifications.py +++ b/ceilometer/compute/notifications.py @@ -172,3 +172,29 @@ class InstanceFlavor(_Base): ) ) return counters + + +class InstanceDelete(_Base): + """Handle the messages sent by the nova notifier plugin + when an instance is being deleted. + """ + + @staticmethod + def get_event_types(): + return ['compute.instance.delete.samples'] + + def process_notification(self, message): + return [ + counter.Counter(name=sample['name'], + type=sample['type'], + unit=sample['unit'], + volume=sample['volume'], + user_id=message['payload']['user_id'], + project_id=message['payload']['tenant_id'], + resource_id=message['payload']['instance_id'], + timestamp=message['timestamp'], + resource_metadata=self.notification_to_metadata( + message), + ) + for sample in message['payload'].get('samples', []) + ] diff --git a/ceilometer/compute/nova_notifier/__init__.py b/ceilometer/compute/nova_notifier/__init__.py new file mode 100644 index 000000000..8b6593827 --- /dev/null +++ b/ceilometer/compute/nova_notifier/__init__.py @@ -0,0 +1,28 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 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. + +# NOTE(dhellmann): The implementations of the notifier for folsom and +# grizzly are completely different. Rather than have lots of checks +# throughout the code, the two implementations are placed in separate +# modules and the right version is imported here. +try: + import nova.conductor +except ImportError: + from .folsom import * +else: + from .grizzly import * diff --git a/ceilometer/compute/nova_notifier.py b/ceilometer/compute/nova_notifier/folsom.py similarity index 93% rename from ceilometer/compute/nova_notifier.py rename to ceilometer/compute/nova_notifier/folsom.py index eebdf6a14..eaced71b9 100644 --- a/ceilometer/compute/nova_notifier.py +++ b/ceilometer/compute/nova_notifier/folsom.py @@ -16,17 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. +__all__ = [ + 'notify', + 'initialize_manager', +] + from oslo.config import cfg from ceilometer.openstack.common import log as logging from ceilometer.compute.manager import AgentManager -try: - from nova.conductor import api - instance_info_source = api.API() -except ImportError: - from nova import db as instance_info_source +from nova import db as instance_info_source # This module runs inside the nova compute # agent, which only configures the "nova" logger. diff --git a/ceilometer/compute/nova_notifier/grizzly.py b/ceilometer/compute/nova_notifier/grizzly.py new file mode 100644 index 000000000..e8f016f5b --- /dev/null +++ b/ceilometer/compute/nova_notifier/grizzly.py @@ -0,0 +1,175 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Author: Julien Danjou +# 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. + +__all__ = [ + 'notify', + 'DeletedInstanceStatsGatherer', + 'initialize_gatherer', + 'instance_info_source', + '_gatherer', # for tests to mock +] + +import sys + +from nova import notifications +from nova.openstack.common.notifier import api as notifier_api +from nova.openstack.common import log as logging + +# HACK(dhellmann): Insert the nova version of openstack.common into +# sys.modules as though it was the copy from ceilometer, so that when +# we use modules from ceilometer below they do not re-define options. +import ceilometer # use the real ceilometer base package +for name in ['openstack', 'openstack.common', 'openstack.common.log']: + sys.modules['ceilometer.' + name] = sys.modules['nova.' + name] + +from nova.conductor import api + +from oslo.config import cfg + +from ceilometer import extension_manager +from ceilometer.compute.virt import inspector + +# This module runs inside the nova compute +# agent, which only configures the "nova" logger. +# We use a fake logger name in that namespace +# so that messages from this module appear +# in the log file. +LOG = logging.getLogger('nova.ceilometer.notifier') + +_gatherer = None +instance_info_source = api.API() + + +class DeletedInstanceStatsGatherer(object): + + def __init__(self, extensions): + self.mgr = extensions + self.inspector = inspector.get_hypervisor_inspector() + + def _get_counters_from_plugin(self, ext, instance, *args, **kwds): + """Used with the extenaion manager map() method.""" + return ext.obj.get_counters(self, instance) + + def __call__(self, instance): + counters = self.mgr.map(self._get_counters_from_plugin, + instance=instance, + ) + # counters is a list of lists, so flatten it before returning + # the results + results = [] + for clist in counters: + results.extend(clist) + return results + + +def initialize_gatherer(gatherer=None): + """Set the callable used to gather stats for the instance. + + gatherer should be a callable accepting one argument (the instance + ref), or None to have a default gatherer used + """ + global _gatherer + if gatherer is not None: + LOG.debug('using provided stats gatherer %r', gatherer) + _gatherer = gatherer + if _gatherer is None: + LOG.debug('making a new stats gatherer') + mgr = extension_manager.ActivatedExtensionManager( + namespace='ceilometer.poll.compute', + disabled_names=cfg.CONF.disabled_compute_pollsters, + ) + _gatherer = DeletedInstanceStatsGatherer(mgr) + return _gatherer + + +class Instance(object): + """Model class for instances + + The pollsters all expect an instance that looks like what the + novaclient gives them, but the conductor API gives us a + dictionary. This class makes an object from the dictonary so we + can pass it to the pollsters. + """ + def __init__(self, info): + for k, v in info.iteritems(): + setattr(self, k, v) + LOG.debug('INFO %r', info) + + @property + def tenant_id(self): + return self.project_id + + @property + def flavor(self): + return { + 'id': self.instance_type_id, + 'name': self.instance_type.get('name', 'UNKNOWN'), + } + + @property + def hostId(self): + return self.host + + @property + def image(self): + return {'id': self.image_ref} + + +def notify(context, message): + if message['event_type'] != 'compute.instance.delete.start': + LOG.debug('ignoring %s', message['event_type']) + return + LOG.info('processing %s', message['event_type']) + gatherer = initialize_gatherer() + + instance_id = message['payload']['instance_id'] + LOG.debug('polling final stats for %r', instance_id) + + # Ask for the instance details + instance_ref = instance_info_source.instance_get_by_uuid( + context, + instance_id, + ) + + # Get the default notification payload + payload = notifications.info_from_instance( + context, instance_ref, None, None) + + # Extend the payload with samples from our plugins. We only need + # to send some of the data from the counter objects, since a lot + # of the fields are the same. + instance = Instance(instance_ref) + counters = gatherer(instance) + payload['samples'] = [{'name': c.name, + 'type': c.type, + 'unit': c.unit, + 'volume': c.volume} + for c in counters] + + publisher_id = notifier_api.publisher_id('compute', None) + + # We could simply modify the incoming message payload, but we + # can't be sure that this notifier will be called before the RPC + # notifier. Modifying the content may also break the message + # signature. So, we start a new message publishing. We will be + # called again recursively as a result, but we ignore the event we + # generate so it doesn't matter. + notifier_api.notify(context, publisher_id, + 'compute.instance.delete.samples', + notifier_api.INFO, payload) diff --git a/ceilometer/compute/virt/inspector.py b/ceilometer/compute/virt/inspector.py index 44cbc4caf..d2222978b 100644 --- a/ceilometer/compute/virt/inspector.py +++ b/ceilometer/compute/virt/inspector.py @@ -3,6 +3,7 @@ # Copyright © 2012 Red Hat, Inc # # Author: Eoghan Glynn +# 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 @@ -19,6 +20,23 @@ import collections +from oslo.config import cfg +from stevedore import driver + +from ceilometer.openstack.common import log + + +OPTS = [ + cfg.StrOpt('hypervisor_inspector', + default='libvirt', + help='Inspector to use for inspecting the hypervisor layer'), +] + +cfg.CONF.register_opts(OPTS) + + +LOG = log.getLogger(__name__) + # Named tuple representing instances. # # name: the name of the instance @@ -128,3 +146,15 @@ class Inspector(object): read and written, and the error count """ raise NotImplementedError() + + +def get_hypervisor_inspector(): + try: + namespace = 'ceilometer.compute.virt' + mgr = driver.DriverManager(namespace, + cfg.CONF.hypervisor_inspector, + invoke_on_load=True) + return mgr.driver + except ImportError as e: + LOG.error("Unable to load the hypervisor inspector: %s" % (e)) + return Inspector() diff --git a/ceilometer/plugin.py b/ceilometer/plugin.py index c1cd5fe13..141a31672 100644 --- a/ceilometer/plugin.py +++ b/ceilometer/plugin.py @@ -21,10 +21,6 @@ import abc from collections import namedtuple -# Import rpc_notifier to register notification_topics flag so that -# plugins can use it -import ceilometer.openstack.common.notifier.rpc_notifier - ExchangeTopics = namedtuple('ExchangeTopics', ['exchange', 'topics']) diff --git a/tests/compute/test_nova_notifier.py b/nova_tests/test_folsom.py similarity index 97% rename from tests/compute/test_nova_notifier.py rename to nova_tests/test_folsom.py index d03371212..67a840163 100644 --- a/tests/compute/test_nova_notifier.py +++ b/nova_tests/test_folsom.py @@ -18,6 +18,13 @@ """Tests for ceilometer.compute.nova_notifier """ +try: + import nova.conductor + import nose.plugins.skip + raise nose.SkipTest('do not run folsom tests under grizzly') +except ImportError: + pass + # FIXME(dhellmann): Temporarily disable these tests so we can get a # fix to go through Jenkins. import nose.plugins.skip @@ -31,13 +38,9 @@ from stevedore import extension from stevedore.tests import manager as test_manager from ceilometer.compute import manager -try: - from nova import config - nova_CONF = config.cfg.CONF -except ImportError: - # XXX Folsom compat - from nova import flags - nova_CONF = flags.FLAGS +# XXX Folsom compat +from nova import flags +nova_CONF = flags.FLAGS from nova import db from nova import context from nova import service # For nova_CONF.compute_manager diff --git a/nova_tests/test_grizzly.py b/nova_tests/test_grizzly.py new file mode 100644 index 000000000..e742a055a --- /dev/null +++ b/nova_tests/test_grizzly.py @@ -0,0 +1,229 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Author: Julien Danjou +# 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. +"""Tests for ceilometer.compute.nova_notifier +""" + +try: + import nova.conductor +except ImportError: + import nose.plugins.skip + raise nose.SkipTest('do not run grizzly tests under folsom') + +import contextlib +import datetime + +import mock + +from oslo.config import cfg + +from stevedore import extension +from stevedore.tests import manager as test_manager + +## NOTE(dhellmann): These imports are not in the generally approved +## alphabetical order, but they are in the order that actually +## works. Please don't change them. + +from nova import config +from nova import db +from nova import context +from nova.tests import fake_network +from nova.compute import vm_states +from nova.openstack.common.notifier import api as notifier_api +from nova.openstack.common import importutils +from nova.openstack.common import log as logging + +# For nova_CONF.compute_manager, used in the nova_notifier module. +from nova import service + +# HACK(dhellmann): Import this before any other ceilometer code +# because the notifier module messes with the import path to force +# nova's version of oslo to be used instead of ceilometer's. +from ceilometer.compute import nova_notifier + +from ceilometer import counter +from ceilometer.tests import base + +LOG = logging.getLogger(__name__) +nova_CONF = config.cfg.CONF + + +class TestNovaNotifier(base.TestCase): + + class Pollster(object): + instances = [] + test_data = counter.Counter( + name='test', + type=counter.TYPE_CUMULATIVE, + unit='units-go-here', + volume=1, + user_id='test', + project_id='test', + resource_id='test_run_tasks', + timestamp=datetime.datetime.utcnow().isoformat(), + resource_metadata={'name': 'Pollster', + }, + ) + + def get_counters(self, manager, instance): + self.instances.append((manager, instance)) + return [self.test_data] + + def get_counter_names(self): + return ['test'] + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def setUp(self): + super(TestNovaNotifier, self).setUp() + nova_CONF.compute_driver = 'nova.virt.fake.FakeDriver' + nova_CONF.notification_driver = [nova_notifier.__name__] + nova_CONF.rpc_backend = 'nova.openstack.common.rpc.impl_fake' + nova_CONF.vnc_enabled = False + nova_CONF.spice.enabled = False + self.compute = importutils.import_object(nova_CONF.compute_manager) + self.context = context.get_admin_context() + fake_network.set_stub_network_methods(self.stubs) + + self.instance = {"name": "instance-1", + 'OS-EXT-SRV-ATTR:instance_name': 'instance-1', + "id": 1, + "image_ref": "FAKE", + "user_id": "FAKE", + "project_id": "FAKE", + "display_name": "FAKE NAME", + "hostname": "abcdef", + "reservation_id": "FAKE RID", + "instance_type_id": 1, + "architecture": "x86", + "memory_mb": "1024", + "root_gb": "20", + "ephemeral_gb": "0", + "vcpus": 1, + "host": "fakehost", + "availability_zone": + "1e3ce043029547f1a61c1996d1a531a4", + "created_at": '2012-05-08 20:23:41', + "os_type": "linux", + "kernel_id": "kernelid", + "ramdisk_id": "ramdiskid", + "vm_state": vm_states.ACTIVE, + "access_ip_v4": "someip", + "access_ip_v6": "someip", + "metadata": {}, + "uuid": "144e08f4-00cb-11e2-888e-5453ed1bbb5f", + "system_metadata": {}} + self.stubs.Set(db, 'instance_info_cache_delete', self.do_nothing) + self.stubs.Set(db, 'instance_destroy', self.do_nothing) + self.stubs.Set(db, 'instance_system_metadata_get', + self.fake_db_instance_system_metadata_get) + self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', + lambda context, instance: {}) + self.stubs.Set(db, 'instance_update_and_get_original', + lambda context, uuid, kwargs: (self.instance, + self.instance)) + + # Set up to capture the notification messages generated by the + # plugin and to invoke our notifier plugin. + self.notifications = [] + notifier_api._reset_drivers() + notifier_api.add_driver(self) + notifier_api.add_driver(nova_notifier) + + ext_mgr = test_manager.TestExtensionManager([ + extension.Extension('test', + None, + None, + self.Pollster(), + ), + ]) + self.ext_mgr = ext_mgr + self.gatherer = nova_notifier.DeletedInstanceStatsGatherer(ext_mgr) + nova_notifier.initialize_gatherer(self.gatherer) + + # Terminate the instance to trigger the notification. + with contextlib.nested( + # Under Grizzly, Nova has moved to no-db access on the + # compute node. The compute manager uses RPC to talk to + # the conductor. We need to disable communication between + # the nova manager and the remote system since we can't + # expect the message bus to be available, or the remote + # controller to be there if the message bus is online. + mock.patch.object(self.compute, 'conductor_api'), + # The code that looks up the instance uses a global + # reference to the API, so we also have to patch that to + # return our fake data. + mock.patch.object(nova_notifier.instance_info_source, + 'instance_get_by_uuid', + self.fake_instance_ref_get), + ): + self.compute.terminate_instance(self.context, + instance=self.instance) + + def tearDown(self): + notifier_api._reset_drivers() + self.Pollster.instances = [] + super(TestNovaNotifier, self).tearDown() + nova_notifier._gatherer = None + + def fake_instance_ref_get(self, context, id_): + if self.instance['uuid'] == id_: + return self.instance + return {} + + @staticmethod + def do_nothing(*args, **kwargs): + pass + + @staticmethod + def fake_db_instance_system_metadata_get(context, uuid): + return dict(meta_a=123, meta_b="foobar") + + def notify(self, context, message): + self.notifications.append(message) + + def test_pollster_called(self): + # The notifier plugin sends another notification for the same + # instance, so we expect to have 2 entries in the list. + self.assertEqual(len(self.Pollster.instances), 2) + + def test_correct_instance(self): + for i, (gatherer, inst) in enumerate(self.Pollster.instances): + self.assertEqual((i, inst.uuid), (i, self.instance['uuid'])) + + def test_correct_gatherer(self): + for i, (gatherer, inst) in enumerate(self.Pollster.instances): + self.assertEqual((i, gatherer), (i, self.gatherer)) + + def test_samples(self): + # Ensure that the outgoing notification looks like what we expect + for message in self.notifications: + event = message['event_type'] + if event != 'compute.instance.delete.samples': + continue + payload = message['payload'] + samples = payload['samples'] + self.assertEqual(len(samples), 1) + s = payload['samples'][0] + self.assertEqual(s, {'name': 'test', + 'type': counter.TYPE_CUMULATIVE, + 'unit': 'units-go-here', + 'volume': 1, + }) + break + else: + assert False, 'Did not find expected event' diff --git a/setup.py b/setup.py index e2c156d96..9fe04fb14 100755 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ setuptools.setup( [ceilometer.collector] instance = ceilometer.compute.notifications:Instance instance_flavor = ceilometer.compute.notifications:InstanceFlavor + instance_delete = ceilometer.compute.notifications:InstanceDelete memory = ceilometer.compute.notifications:Memory vcpus = ceilometer.compute.notifications:VCpus disk_root_size = ceilometer.compute.notifications:RootDiskSize diff --git a/tests/compute/test_notifications.py b/tests/compute/test_notifications.py index b281a6cc4..0e541b0a3 100644 --- a/tests/compute/test_notifications.py +++ b/tests/compute/test_notifications.py @@ -285,13 +285,79 @@ INSTANCE_RESIZE_REVERT_END = { u'priority': u'INFO' } +INSTANCE_DELETE_SAMPLES = { + u'_context_roles': [u'admin'], + u'_context_request_id': u'req-9da1d714-dabe-42fd-8baa-583e57cd4f1a', + u'_context_quota_class': None, + u'event_type': u'compute.instance.delete.samples', + u'_context_user_name': u'admin', + u'_context_project_name': u'admin', + u'timestamp': u'2013-01-04 15:20:32.009532', + u'_context_is_admin': True, + u'message_id': u'c48deeba-d0c3-4154-b3db-47480b52267a', + u'_context_auth_token': None, + u'_context_instance_lock_checked': False, + u'_context_project_id': u'cea4b25edb484e5392727181b7721d29', + u'_context_timestamp': u'2013-01-04T15:19:51.018218', + u'_context_read_deleted': u'no', + u'_context_user_id': u'01b83a5e23f24a6fb6cd073c0aee6eed', + u'_context_remote_address': u'10.147.132.184', + u'publisher_id': u'compute.ip-10-147-132-184.ec2.internal', + u'payload': {u'state_description': u'resize_reverting', + u'availability_zone': None, + u'ephemeral_gb': 0, + u'instance_type_id': 2, + u'deleted_at': u'', + u'reservation_id': u'r-u3fvim06', + u'memory_mb': 512, + u'user_id': u'01b83a5e23f24a6fb6cd073c0aee6eed', + u'hostname': u's1', + u'state': u'resized', + u'launched_at': u'2013-01-04T15:10:14.000000', + u'metadata': [], + u'ramdisk_id': u'5f23128e-5525-46d8-bc66-9c30cd87141a', + u'access_ip_v6': None, + u'disk_gb': 0, + u'access_ip_v4': None, + u'kernel_id': u'571478e0-d5e7-4c2e-95a5-2bc79443c28a', + u'host': u'ip-10-147-132-184.ec2.internal', + u'display_name': u's1', + u'image_ref_url': u'http://10.147.132.184:9292/images/' + 'a130b9d9-e00e-436e-9782-836ccef06e8a', + u'root_gb': 0, + u'tenant_id': u'cea4b25edb484e5392727181b7721d29', + u'created_at': u'2013-01-04T11:21:48.000000', + u'instance_id': u'648e8963-6886-4c3c-98f9-4511c292f86b', + u'instance_type': u'm1.tiny', + u'vcpus': 1, + u'image_meta': {u'kernel_id': + u'571478e0-d5e7-4c2e-95a5-2bc79443c28a', + u'ramdisk_id': + u'5f23128e-5525-46d8-bc66-9c30cd87141a', + u'base_image_ref': + u'a130b9d9-e00e-436e-9782-836ccef06e8a'}, + u'architecture': None, + u'os_type': None, + u'samples': [{u'name': u'sample-name1', + u'type': u'sample-type1', + u'unit': u'sample-units1', + u'volume': 1}, + {u'name': u'sample-name2', + u'type': u'sample-type2', + u'unit': u'sample-units2', + u'volume': 2}, + ], + }, + u'priority': u'INFO' +} + class TestNotifications(unittest.TestCase): + def test_process_notification(self): info = notifications.Instance().process_notification( INSTANCE_CREATE_END )[0] - for name, actual, expected in [ ('counter_name', info.name, 'instance'), ('counter_type', info.type, counter.TYPE_GAUGE), @@ -435,3 +501,10 @@ class TestNotifications(unittest.TestCase): c = counters[0] self.assertEqual(c.volume, INSTANCE_RESIZE_REVERT_END['payload']['vcpus']) + + def test_instance_delete_samples(self): + ic = notifications.InstanceDelete() + counters = ic.process_notification(INSTANCE_DELETE_SAMPLES) + self.assertEqual(len(counters), 2) + names = [c.name for c in counters] + self.assertEqual(names, ['sample-name1', 'sample-name2']) diff --git a/tests/compute/test_pollsters.py b/tests/compute/test_pollsters.py index 65b64a3cc..34518546d 100644 --- a/tests/compute/test_pollsters.py +++ b/tests/compute/test_pollsters.py @@ -33,9 +33,9 @@ class TestPollsterBase(test_base.TestCase): def setUp(self): super(TestPollsterBase, self).setUp() - self.mox.StubOutWithMock(manager, 'get_hypervisor_inspector') + self.mox.StubOutWithMock(virt_inspector, 'get_hypervisor_inspector') self.inspector = self.mox.CreateMock(virt_inspector.Inspector) - manager.get_hypervisor_inspector().AndReturn(self.inspector) + virt_inspector.get_hypervisor_inspector().AndReturn(self.inspector) self.instance = mock.MagicMock() self.instance.name = 'instance-00000001' setattr(self.instance, 'OS-EXT-SRV-ATTR:instance_name', diff --git a/tox.ini b/tox.ini index 754ff0be6..b64505cc3 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ setenv = VIRTUAL_ENV={envdir} NOSE_OPENSTACK_YELLOW=0.025 NOSE_OPENSTACK_SHOW_ELAPSED=1 commands = + nosetests --no-path-adjustment --where=./nova_tests nosetests --no-path-adjustment --where=./tests sitepackages = False