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
|
include ceilometer/storage/sqlalchemy/migrate_repo/migrate.cfg
|
||||||
exclude .gitignore
|
exclude .gitignore
|
||||||
exclude .gitreview
|
exclude .gitreview
|
||||||
|
recursive-include tests *.py
|
||||||
|
recursive-include nova_tests *.py
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
recursive-include public *
|
recursive-include public *
|
||||||
recursive-include ceilometer/locale *
|
recursive-include ceilometer/locale *
|
||||||
|
@ -29,6 +29,10 @@ from ceilometer.openstack.common import log
|
|||||||
from ceilometer.openstack.common import timeutils
|
from ceilometer.openstack.common import timeutils
|
||||||
from ceilometer.openstack.common.rpc import dispatcher as rpc_dispatcher
|
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 = [
|
OPTS = [
|
||||||
cfg.ListOpt('disabled_notification_listeners',
|
cfg.ListOpt('disabled_notification_listeners',
|
||||||
@ -56,6 +60,7 @@ class CollectorService(service.PeriodicService):
|
|||||||
|
|
||||||
def initialize_service_hook(self, service):
|
def initialize_service_hook(self, service):
|
||||||
'''Consumers must be declared before consume_thread start.'''
|
'''Consumers must be declared before consume_thread start.'''
|
||||||
|
LOG.debug('initialize_service_hooks')
|
||||||
publisher_manager = dispatch.NameDispatchExtensionManager(
|
publisher_manager = dispatch.NameDispatchExtensionManager(
|
||||||
namespace=pipeline.PUBLISHER_NAMESPACE,
|
namespace=pipeline.PUBLISHER_NAMESPACE,
|
||||||
check_func=lambda x: True,
|
check_func=lambda x: True,
|
||||||
@ -63,6 +68,8 @@ class CollectorService(service.PeriodicService):
|
|||||||
)
|
)
|
||||||
self.pipeline_manager = pipeline.setup_pipeline(publisher_manager)
|
self.pipeline_manager = pipeline.setup_pipeline(publisher_manager)
|
||||||
|
|
||||||
|
LOG.debug('loading notification handlers from %s',
|
||||||
|
self.COLLECTOR_NAMESPACE)
|
||||||
self.notification_manager = \
|
self.notification_manager = \
|
||||||
extension_manager.ActivatedExtensionManager(
|
extension_manager.ActivatedExtensionManager(
|
||||||
namespace=self.COLLECTOR_NAMESPACE,
|
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,
|
'host': instance.hostId,
|
||||||
# Image properties
|
# Image properties
|
||||||
'image_ref': (instance.image['id'] if instance.image else None),
|
'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:
|
for name in INSTANCE_PROPERTIES:
|
||||||
metadata[name] = getattr(instance, name, u'')
|
metadata[name] = getattr(instance, name, u'')
|
||||||
return metadata
|
return metadata
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
from stevedore import driver
|
|
||||||
|
|
||||||
from ceilometer import agent
|
from ceilometer import agent
|
||||||
from ceilometer import extension_manager
|
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.compute.virt import inspector as virt_inspector
|
||||||
from ceilometer.openstack.common import log
|
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__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -64,18 +51,6 @@ class PollingTask(agent.PollingTask):
|
|||||||
self.manager.nv.instance_get_all_by_host(cfg.CONF.host))
|
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):
|
class AgentManager(agent.AgentManager):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -85,7 +60,7 @@ class AgentManager(agent.AgentManager):
|
|||||||
disabled_names=cfg.CONF.disabled_compute_pollsters,
|
disabled_names=cfg.CONF.disabled_compute_pollsters,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self._inspector = get_hypervisor_inspector()
|
self._inspector = virt_inspector.get_hypervisor_inspector()
|
||||||
self.nv = nova_client.Client()
|
self.nv = nova_client.Client()
|
||||||
|
|
||||||
def create_polling_task(self):
|
def create_polling_task(self):
|
||||||
|
@ -172,3 +172,29 @@ class InstanceFlavor(_Base):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return counters
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'notify',
|
||||||
|
'initialize_manager',
|
||||||
|
]
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from ceilometer.openstack.common import log as logging
|
from ceilometer.openstack.common import log as logging
|
||||||
|
|
||||||
from ceilometer.compute.manager import AgentManager
|
from ceilometer.compute.manager import AgentManager
|
||||||
|
|
||||||
try:
|
from nova import db as instance_info_source
|
||||||
from nova.conductor import api
|
|
||||||
instance_info_source = api.API()
|
|
||||||
except ImportError:
|
|
||||||
from nova import db as instance_info_source
|
|
||||||
|
|
||||||
# This module runs inside the nova compute
|
# This module runs inside the nova compute
|
||||||
# agent, which only configures the "nova" logger.
|
# 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
|
# Copyright © 2012 Red Hat, Inc
|
||||||
#
|
#
|
||||||
# Author: Eoghan Glynn <eglynn@redhat.com>
|
# Author: Eoghan Glynn <eglynn@redhat.com>
|
||||||
|
# Doug Hellmann <doug.hellmann@dreamhost.com>
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -19,6 +20,23 @@
|
|||||||
|
|
||||||
import collections
|
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.
|
# Named tuple representing instances.
|
||||||
#
|
#
|
||||||
# name: the name of the instance
|
# name: the name of the instance
|
||||||
@ -128,3 +146,15 @@ class Inspector(object):
|
|||||||
read and written, and the error count
|
read and written, and the error count
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
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
|
import abc
|
||||||
from collections import namedtuple
|
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'])
|
ExchangeTopics = namedtuple('ExchangeTopics', ['exchange', 'topics'])
|
||||||
|
|
||||||
|
@ -18,6 +18,13 @@
|
|||||||
"""Tests for ceilometer.compute.nova_notifier
|
"""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
|
# FIXME(dhellmann): Temporarily disable these tests so we can get a
|
||||||
# fix to go through Jenkins.
|
# fix to go through Jenkins.
|
||||||
import nose.plugins.skip
|
import nose.plugins.skip
|
||||||
@ -31,13 +38,9 @@ from stevedore import extension
|
|||||||
from stevedore.tests import manager as test_manager
|
from stevedore.tests import manager as test_manager
|
||||||
from ceilometer.compute import manager
|
from ceilometer.compute import manager
|
||||||
|
|
||||||
try:
|
# XXX Folsom compat
|
||||||
from nova import config
|
from nova import flags
|
||||||
nova_CONF = config.cfg.CONF
|
nova_CONF = flags.FLAGS
|
||||||
except ImportError:
|
|
||||||
# XXX Folsom compat
|
|
||||||
from nova import flags
|
|
||||||
nova_CONF = flags.FLAGS
|
|
||||||
from nova import db
|
from nova import db
|
||||||
from nova import context
|
from nova import context
|
||||||
from nova import service # For nova_CONF.compute_manager
|
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]
|
[ceilometer.collector]
|
||||||
instance = ceilometer.compute.notifications:Instance
|
instance = ceilometer.compute.notifications:Instance
|
||||||
instance_flavor = ceilometer.compute.notifications:InstanceFlavor
|
instance_flavor = ceilometer.compute.notifications:InstanceFlavor
|
||||||
|
instance_delete = ceilometer.compute.notifications:InstanceDelete
|
||||||
memory = ceilometer.compute.notifications:Memory
|
memory = ceilometer.compute.notifications:Memory
|
||||||
vcpus = ceilometer.compute.notifications:VCpus
|
vcpus = ceilometer.compute.notifications:VCpus
|
||||||
disk_root_size = ceilometer.compute.notifications:RootDiskSize
|
disk_root_size = ceilometer.compute.notifications:RootDiskSize
|
||||||
|
@ -285,13 +285,79 @@ INSTANCE_RESIZE_REVERT_END = {
|
|||||||
u'priority': u'INFO'
|
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):
|
class TestNotifications(unittest.TestCase):
|
||||||
|
|
||||||
def test_process_notification(self):
|
def test_process_notification(self):
|
||||||
info = notifications.Instance().process_notification(
|
info = notifications.Instance().process_notification(
|
||||||
INSTANCE_CREATE_END
|
INSTANCE_CREATE_END
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
for name, actual, expected in [
|
for name, actual, expected in [
|
||||||
('counter_name', info.name, 'instance'),
|
('counter_name', info.name, 'instance'),
|
||||||
('counter_type', info.type, counter.TYPE_GAUGE),
|
('counter_type', info.type, counter.TYPE_GAUGE),
|
||||||
@ -435,3 +501,10 @@ class TestNotifications(unittest.TestCase):
|
|||||||
c = counters[0]
|
c = counters[0]
|
||||||
self.assertEqual(c.volume,
|
self.assertEqual(c.volume,
|
||||||
INSTANCE_RESIZE_REVERT_END['payload']['vcpus'])
|
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):
|
def setUp(self):
|
||||||
super(TestPollsterBase, self).setUp()
|
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)
|
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 = mock.MagicMock()
|
||||||
self.instance.name = 'instance-00000001'
|
self.instance.name = 'instance-00000001'
|
||||||
setattr(self.instance, 'OS-EXT-SRV-ATTR:instance_name',
|
setattr(self.instance, 'OS-EXT-SRV-ATTR:instance_name',
|
||||||
|
1
tox.ini
1
tox.ini
@ -11,6 +11,7 @@ setenv = VIRTUAL_ENV={envdir}
|
|||||||
NOSE_OPENSTACK_YELLOW=0.025
|
NOSE_OPENSTACK_YELLOW=0.025
|
||||||
NOSE_OPENSTACK_SHOW_ELAPSED=1
|
NOSE_OPENSTACK_SHOW_ELAPSED=1
|
||||||
commands =
|
commands =
|
||||||
|
nosetests --no-path-adjustment --where=./nova_tests
|
||||||
nosetests --no-path-adjustment --where=./tests
|
nosetests --no-path-adjustment --where=./tests
|
||||||
sitepackages = False
|
sitepackages = False
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user