Decouple the nova notifier from ceilometer code
The move to oslo.config introduced a conflict in the nova notifier because both nova and ceilometer have copies of the openstack.common.rpc library and define an option for the AMQP exchange name for the project. This changeset decouples the notifier plugin in ceilometer from most of the ceilometer code, to remove that conflict. The nova notifier is rewritten to emit a new notification message with event type `compute.instance.delete.samples` instead of making the old RPC calls directly to the ceilometer collector, and a notification listener plugin is provided to convert those messages to sample data. The notifier implementations are split between the one that worked in folsom and the one that works in grizzly to maintain backwards compatibility. get_hypervisor_inspector() is moved to a location where it can be imported both by the compute agent manager and the notifier plugin. The definition of `disabled_compute_pollsters` option is also moved for the same reason. The tox configuration is changed to run the nova notifier tests separately from the other tests, since nose cannot import nova and ceilometer code in the same process. bug 1130952 Change-Id: I39ba4564c9c14f09dbdd768d7a83f6940e3942ad Signed-off-by: Doug Hellmann <doug.hellmann@dreamhost.com>
This commit is contained in:
parent
1d44a182cb
commit
7d8bd50d08
@ -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