Remove database access from agent pollsters

Fixes bug #1012242.

Patch stolen from John Tran <jhtran@att.com>

Change-Id: Iab59eb752199e0cd3c8134a29e05b53356a30d75
This commit is contained in:
Julien Danjou 2012-10-08 22:14:46 +00:00
parent 2a82834fb9
commit c9247f1069
12 changed files with 159 additions and 122 deletions

View File

@ -17,7 +17,6 @@
# under the License.
from ceilometer import extension_manager
from ceilometer.compute import resources
from ceilometer.openstack.common import cfg
from ceilometer.openstack.common import log
from ceilometer import publish
@ -40,7 +39,6 @@ class AgentManager(object):
def __init__(self, host=None):
super(AgentManager, self).__init__()
self.host = host
self.resources = resources.Resources()
self.ext_manager = extension_manager.ActivatedExtensionManager(
namespace=PLUGIN_NAMESPACE,
disabled_names=cfg.CONF.disabled_central_pollsters,

View File

@ -20,7 +20,6 @@
INSTANCE_PROPERTIES = [
# Identity properties
'display_name',
'reservation_id',
# Type properties
'architecture',
@ -41,16 +40,16 @@ INSTANCE_PROPERTIES = [
]
def get_metadata_from_dbobject(instance):
def get_metadata_from_object(instance):
"""Return a metadata dictionary for the instance.
"""
metadata = {
'display_name': instance.display_name,
'instance_type': (instance.instance_type.flavorid
if instance.instance_type
else None),
'host': instance.host,
'display_name': instance.name,
'name': getattr(instance, 'OS-EXT-SRV-ATTR:instance_name', u''),
'instance_type': (instance.flavor['id'] if instance.flavor else None),
'host': instance.hostId,
}
for name in INSTANCE_PROPERTIES:
metadata[name] = instance.get(name, u'')
metadata[name] = getattr(instance, name, u'')
return metadata

View File

@ -34,6 +34,11 @@ from ceilometer.openstack.common import timeutils
FLAGS = flags.FLAGS
def _instance_name(instance):
"""Shortcut to get instance name"""
return getattr(instance, 'OS-EXT-SRV-ATTR:instance_name', None)
def get_libvirt_connection():
"""Return an open connection for talking to libvirt."""
# The direct-import implementation only works with Folsom because
@ -53,11 +58,10 @@ def make_counter_from_instance(instance, name, type, volume):
type=type,
volume=volume,
user_id=instance.user_id,
project_id=instance.project_id,
resource_id=instance.uuid,
project_id=instance.tenant_id,
resource_id=instance.id,
timestamp=timeutils.isotime(),
resource_metadata=compute_instance.get_metadata_from_dbobject(
instance),
resource_metadata=compute_instance.get_metadata_from_object(instance),
)
@ -110,11 +114,12 @@ class DiskIOPollster(LibVirtPollster):
conn = get_libvirt_connection()
# TODO(jd) This does not work see bug#998089
# for disk in conn.get_disks(instance.name):
instance_name = _instance_name(instance)
try:
disks = self._get_disks(conn, instance.name)
disks = self._get_disks(conn, instance_name)
except Exception as err:
self.LOG.warning('Ignoring instance %s: %s',
instance.name, err)
instance_name, err)
self.LOG.exception(err)
else:
r_bytes = 0
@ -122,7 +127,7 @@ class DiskIOPollster(LibVirtPollster):
w_bytes = 0
w_requests = 0
for disk in disks:
stats = conn.block_stats(instance.name, disk)
stats = conn.block_stats(instance_name, disk)
self.LOG.info(self.DISKIO_USAGE_MESSAGE,
instance, disk, stats[0], stats[1],
stats[2], stats[3], stats[4])
@ -159,14 +164,14 @@ class CPUPollster(LibVirtPollster):
utilization_map = {}
def get_cpu_util(self, instance, cpu_info):
prev_times = self.utilization_map.get(instance.uuid)
self.utilization_map[instance.uuid] = (cpu_info['cpu_time'],
datetime.datetime.now())
prev_times = self.utilization_map.get(instance.id)
self.utilization_map[instance.id] = (cpu_info['cpu_time'],
datetime.datetime.now())
cpu_util = 0.0
if prev_times:
prev_cpu = prev_times[0]
prev_timestamp = prev_times[1]
delta = self.utilization_map[instance.uuid][1] - prev_timestamp
delta = self.utilization_map[instance.id][1] - prev_timestamp
elapsed = (delta.seconds * (10 ** 6) + delta.microseconds) * 1000
cores_fraction = instance.vcpus * 1.0 / multiprocessing.cpu_count()
# account for cpu_time being reset when the instance is restarted
@ -178,9 +183,9 @@ class CPUPollster(LibVirtPollster):
def get_counters(self, manager, instance):
conn = get_libvirt_connection()
self.LOG.info('checking instance %s', instance.uuid)
self.LOG.info('checking instance %s', instance.id)
try:
cpu_info = conn.get_info(instance)
cpu_info = conn.get_info({'name': _instance_name(instance)})
self.LOG.info("CPUTIME USAGE: %s %d",
dict(instance), cpu_info['cpu_time'])
cpu_util = self.get_cpu_util(instance, cpu_info)
@ -203,7 +208,7 @@ class CPUPollster(LibVirtPollster):
)
except Exception as err:
self.LOG.error('could not get CPU time for %s: %s',
instance.uuid, err)
instance.id, err)
self.LOG.exception(err)
@ -216,7 +221,7 @@ class NetPollster(LibVirtPollster):
def _get_vnics(self, conn, instance):
"""Get disks of an instance, only used to bypass bug#998089."""
domain = conn._conn.lookupByName(instance.name)
domain = conn._conn.lookupByName(_instance_name(instance))
tree = etree.fromstring(domain.XMLDesc(0))
vnics = []
for interface in tree.findall('devices/interface'):
@ -232,14 +237,14 @@ class NetPollster(LibVirtPollster):
@staticmethod
def make_vnic_counter(instance, name, type, volume, vnic_data):
resource_metadata = copy.copy(vnic_data)
resource_metadata['instance_id'] = instance.uuid
resource_metadata['instance_id'] = instance.id
return counter.Counter(
name=name,
type=type,
volume=volume,
user_id=instance.user_id,
project_id=instance.project_id,
project_id=instance.tenant_id,
resource_id=vnic_data['fref'],
timestamp=timeutils.isotime(),
resource_metadata=resource_metadata
@ -247,20 +252,21 @@ class NetPollster(LibVirtPollster):
def get_counters(self, manager, instance):
conn = get_libvirt_connection()
self.LOG.info('checking instance %s', instance.uuid)
instance_name = _instance_name(instance)
self.LOG.info('checking instance %s', instance.id)
try:
vnics = self._get_vnics(conn, instance)
except Exception as err:
self.LOG.warning('Ignoring instance %s: %s',
instance.name, err)
instance_name, err)
self.LOG.exception(err)
else:
domain = conn._conn.lookupByName(instance.name)
domain = conn._conn.lookupByName(instance_name)
for vnic in vnics:
rx_bytes, rx_packets, _, _, \
tx_bytes, tx_packets, _, _ = \
domain.interfaceStats(vnic['name'])
self.LOG.info(self.NET_USAGE_MESSAGE, instance.name,
self.LOG.info(self.NET_USAGE_MESSAGE, instance_name,
vnic['name'], rx_bytes, tx_bytes)
yield self.make_vnic_counter(instance,
name='network.incoming.bytes',

View File

@ -17,9 +17,10 @@
# under the License.
from ceilometer import extension_manager
from ceilometer import nova_client
from ceilometer.openstack.common import cfg
from ceilometer.openstack.common import log
from ceilometer.compute import resources
from ceilometer import publish
OPTS = [
@ -40,8 +41,6 @@ PLUGIN_NAMESPACE = 'ceilometer.poll.compute'
class AgentManager(object):
def __init__(self):
self.resources = resources.Resources()
self.ext_manager = extension_manager.ActivatedExtensionManager(
namespace=PLUGIN_NAMESPACE,
disabled_names=cfg.CONF.disabled_compute_pollsters,
@ -76,6 +75,7 @@ class AgentManager(object):
def periodic_tasks(self, context, raise_on_error=False):
"""Tasks to be run at a periodic interval."""
for instance in self.resources.instance_get_all_by_host(context):
if instance['vm_state'] != 'error':
nv = nova_client.Client()
for instance in nv.instance_get_all_by_host(cfg.CONF.host):
if getattr(instance, 'OS-EXT-STS:vm_state', None) != 'error':
self.poll_instance(context, instance)

View File

@ -1,37 +0,0 @@
#
# 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 ceilometer.openstack.common import cfg
from nova.openstack.common import importutils
db_driver_opt = cfg.StrOpt('db_driver',
default='nova.db',
help='driver to use for database access')
cfg.CONF.register_opt(db_driver_opt)
# FIXME(dhellmann): How do we get a list of instances without
# talking directly to the database?
class Resources(object):
def __init__(self):
self.db = importutils.import_module(cfg.CONF.db_driver)
def instance_get_all_by_host(self, context):
return self.db.instance_get_all_by_host(context, cfg.CONF.host)
def floating_ip_get_all(self, context):
return self.db.floating_ip_get_all(context)

View File

@ -22,6 +22,7 @@ from ceilometer.openstack.common import log
from ceilometer.openstack.common import timeutils
from ceilometer import counter
from ceilometer import nova_client
from ceilometer.central import plugin
@ -30,27 +31,21 @@ class FloatingIPPollster(plugin.CentralPollster):
LOG = log.getLogger(__name__ + '.floatingip')
def get_counters(self, manager, context):
try:
ips = manager.resources.floating_ip_get_all(context)
except exception.NoFloatingIpsDefined:
pass
except exception.FloatingIpNotFoundForHost:
pass
else:
for ip in ips:
self.LOG.info("FLOATING IP USAGE: %s" % ip.address)
yield counter.Counter(
name='ip.floating',
type=counter.TYPE_GAUGE,
volume=1,
user_id=None,
project_id=ip.project_id,
resource_id=ip.id,
timestamp=timeutils.utcnow().isoformat(),
resource_metadata={
'address': ip.address,
'fixed_ip_id': ip.fixed_ip_id,
'host': ip.host,
'pool': ip.pool,
'auto_assigned': ip.auto_assigned
})
nv = nova_client.Client()
for ip in nv.floating_ip_get_all():
self.LOG.info("FLOATING IP USAGE: %s" % ip.address)
yield counter.Counter(
name='ip.floating',
type=counter.TYPE_GAUGE,
volume=1,
user_id=None,
project_id=ip.project_id,
resource_id=ip.id,
timestamp=timeutils.utcnow().isoformat(),
resource_metadata={
'address': ip.address,
'fixed_ip_id': ip.fixed_ip_id,
'host': ip.host,
'pool': ip.pool,
'auto_assigned': ip.auto_assigned
})

62
ceilometer/nova_client.py Normal file
View File

@ -0,0 +1,62 @@
# -*- encoding: utf-8 -*-
#
# Author: John Tran <jhtran@att.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.
import os
from functools import wraps
from novaclient.v1_1 import client as nova_client
import ceilometer.service
from ceilometer.openstack.common import cfg, log
LOG = log.getLogger(__name__)
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception, e:
LOG.exception(e)
raise
return with_logging
class Client(object):
def __init__(self):
"""Returns nova client"""
conf = cfg.CONF
tenant = conf.os_tenant_id and conf.os_tenant_id or conf.os_tenant_name
self.nova_client = nova_client.Client(username=cfg.CONF.os_username,
api_key=cfg.CONF.os_password,
project_id=tenant,
auth_url=cfg.CONF.os_auth_url,
no_cache=True)
@logged
def instance_get_all_by_host(self, hostname):
"""Returns list of instances on particular host"""
search_opts = {'host': hostname, 'all_tenants': True}
return self.nova_client.servers.list(detailed=True,
search_opts=search_opts)
@logged
def floating_ip_get_all(self):
"""Returns all floating ips"""
return self.nova_client.floating_ips.list()

View File

@ -44,7 +44,9 @@ class FauxInstance(object):
class TestLocationMetadata(unittest.TestCase):
INSTANCE_PROPERTIES = {'display_name': 'display name',
# Mimics an instance returned from nova api call
INSTANCE_PROPERTIES = {'name': 'display name',
'OS-EXT-SRV-ATTR:instance_name': 'instance-000001',
'reservation_id': 'reservation id',
'architecture': 'x86_64',
'availability_zone': 'zone1',
@ -58,6 +60,8 @@ class TestLocationMetadata(unittest.TestCase):
'memory_mb': 2048,
'root_gb': 3,
'vcpus': 1,
'flavor': {'id': 1},
'hostId': '1234-5678'
}
def setUp(self):
@ -70,9 +74,18 @@ class TestLocationMetadata(unittest.TestCase):
self.instance.instance_type = m
def test_metadata(self):
md = instance.get_metadata_from_dbobject(self.instance)
for name in self.INSTANCE_PROPERTIES.keys():
md = instance.get_metadata_from_object(self.instance)
iprops = self.INSTANCE_PROPERTIES
for name in md.keys():
actual = md[name]
print 'checking', name, actual
expected = self.INSTANCE_PROPERTIES[name]
assert actual == expected
if name == 'name':
assert actual == iprops['OS-EXT-SRV-ATTR:instance_name']
elif name == 'host':
assert actual == iprops['hostId']
elif name == 'display_name':
assert actual == iprops['name']
elif name == 'instance_type':
assert actual == iprops['flavor']['id']
else:
assert actual == iprops[name]

View File

@ -56,6 +56,8 @@ class TestLibvirtBase(test_base.TestCase):
self.manager = manager.AgentManager()
self.instance = mock.MagicMock()
self.instance.name = 'instance-00000001'
setattr(self.instance, 'OS-EXT-SRV-ATTR:instance_name',
self.instance.name)
self.instance.id = 1
self.instance.instance_type = mock.MagicMock()
self.instance.instance_type.name = 'm1.small'
@ -217,10 +219,13 @@ class TestCPUPollster(TestLibvirtBase):
self.instance.vcpus = 1
conn = fake_libvirt_conn(self.mox, 3)
self.mox.StubOutWithMock(conn, 'get_info')
conn.get_info(self.instance).AndReturn({'cpu_time': 1 * (10 ** 6)})
conn.get_info(self.instance).AndReturn({'cpu_time': 3 * (10 ** 6)})
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 1 * (10 ** 6)})
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 3 * (10 ** 6)})
# cpu_time resets on instance restart
conn.get_info(self.instance).AndReturn({'cpu_time': 2 * (10 ** 6)})
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 2 * (10 ** 6)})
self.mox.ReplayAll()
def _verify_cpu_metering(zero, expected_time):

View File

@ -23,6 +23,7 @@ import mock
from stevedore import extension
from ceilometer import nova_client
from ceilometer.compute import manager
from ceilometer import counter
from ceilometer import publish
@ -77,24 +78,18 @@ class TestRunTasks(base.TestCase):
# Set up a fake instance value to be returned by
# instance_get_all_by_host() so when the manager gets the list
# of instances to poll we can control the results.
self.instance = mock.MagicMock()
self.instance.name = 'faux'
self.instance.vm_state = 'active'
stillborn_instance = mock.MagicMock()
stillborn_instance.name = 'stillborn'
stillborn_instance.vm_state = 'error'
self.mox.StubOutWithMock(self.mgr.resources,
'instance_get_all_by_host')
self.mgr.resources.instance_get_all_by_host(
None
).AndReturn([self.instance, stillborn_instance])
self.instance = {'name': 'faux',
'OS-EXT-STS:vm_state': 'active'}
stillborn_instance = {'name': 'stillborn',
'OS-EXT-STS:vm_state': 'error'}
self.stubs.Set(nova_client.Client, 'instance_get_all_by_host',
lambda *x: [self.instance, stillborn_instance])
self.mox.ReplayAll()
# Invoke the periodic tasks to call the pollsters.
self.mgr.periodic_tasks(None)
def test_message(self):
assert len(self.Pollster.counters) == 2
self.assertEqual(len(self.Pollster.counters), 2)
assert self.Pollster.counters[0][1] is self.instance
def test_notifications(self):

View File

@ -19,8 +19,7 @@
import mock
from nova import db
from ceilometer import nova_client
from ceilometer.network import floatingip
from ceilometer.central import manager
from ceilometer.openstack.common import context
@ -34,9 +33,10 @@ class TestFloatingIPPollster(base.TestCase):
self.context = context.get_admin_context()
self.manager = manager.AgentManager()
self.pollster = floatingip.FloatingIPPollster()
self.stubs.Set(db, 'floating_ip_get_all', self.faux_get_ips)
self.stubs.Set(nova_client.Client, 'floating_ip_get_all',
self.faux_get_ips)
def faux_get_ips(self, context):
def faux_get_ips(self):
ips = []
for i in range(1, 4):
ip = mock.MagicMock()

View File

@ -12,3 +12,4 @@ Flask==0.9
stevedore>=0.6
python-glanceclient
python-cinderclient
python-novaclient