Merge "Decouple the nova notifier from ceilometer code"
This commit is contained in:
commit
ca064bdbd6
@ -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 *
|
||||
|
@ -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,
|
||||
|
@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2013 New Dream Network, LLC
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
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)
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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', [])
|
||||
]
|
||||
|
28
ceilometer/compute/nova_notifier/__init__.py
Normal file
28
ceilometer/compute/nova_notifier/__init__.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2013 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# 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 *
|
@ -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.
|
175
ceilometer/compute/nova_notifier/grizzly.py
Normal file
175
ceilometer/compute/nova_notifier/grizzly.py
Normal file
@ -0,0 +1,175 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Julien Danjou <julien@danjou.info>
|
||||
# Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
__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)
|
@ -3,6 +3,7 @@
|
||||
# Copyright © 2012 Red Hat, Inc
|
||||
#
|
||||
# Author: Eoghan Glynn <eglynn@redhat.com>
|
||||
# Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@ -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()
|
||||
|
@ -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'])
|
||||
|
||||
|
@ -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
|
229
nova_tests/test_grizzly.py
Normal file
229
nova_tests/test_grizzly.py
Normal file
@ -0,0 +1,229 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# Author: Julien Danjou <julien@danjou.info>
|
||||
# Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""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'
|
1
setup.py
1
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
|
||||
|
@ -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'])
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user