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

View File

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

View File

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

View File

@ -17,9 +17,10 @@
# under the License. # under the License.
from ceilometer import extension_manager from ceilometer import extension_manager
from ceilometer import nova_client
from ceilometer.openstack.common import cfg from ceilometer.openstack.common import cfg
from ceilometer.openstack.common import log from ceilometer.openstack.common import log
from ceilometer.compute import resources
from ceilometer import publish from ceilometer import publish
OPTS = [ OPTS = [
@ -40,8 +41,6 @@ PLUGIN_NAMESPACE = 'ceilometer.poll.compute'
class AgentManager(object): class AgentManager(object):
def __init__(self): def __init__(self):
self.resources = resources.Resources()
self.ext_manager = extension_manager.ActivatedExtensionManager( self.ext_manager = extension_manager.ActivatedExtensionManager(
namespace=PLUGIN_NAMESPACE, namespace=PLUGIN_NAMESPACE,
disabled_names=cfg.CONF.disabled_compute_pollsters, disabled_names=cfg.CONF.disabled_compute_pollsters,
@ -76,6 +75,7 @@ class AgentManager(object):
def periodic_tasks(self, context, raise_on_error=False): def periodic_tasks(self, context, raise_on_error=False):
"""Tasks to be run at a periodic interval.""" """Tasks to be run at a periodic interval."""
for instance in self.resources.instance_get_all_by_host(context): nv = nova_client.Client()
if instance['vm_state'] != 'error': 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) 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.openstack.common import timeutils
from ceilometer import counter from ceilometer import counter
from ceilometer import nova_client
from ceilometer.central import plugin from ceilometer.central import plugin
@ -30,27 +31,21 @@ class FloatingIPPollster(plugin.CentralPollster):
LOG = log.getLogger(__name__ + '.floatingip') LOG = log.getLogger(__name__ + '.floatingip')
def get_counters(self, manager, context): def get_counters(self, manager, context):
try: nv = nova_client.Client()
ips = manager.resources.floating_ip_get_all(context) for ip in nv.floating_ip_get_all():
except exception.NoFloatingIpsDefined: self.LOG.info("FLOATING IP USAGE: %s" % ip.address)
pass yield counter.Counter(
except exception.FloatingIpNotFoundForHost: name='ip.floating',
pass type=counter.TYPE_GAUGE,
else: volume=1,
for ip in ips: user_id=None,
self.LOG.info("FLOATING IP USAGE: %s" % ip.address) project_id=ip.project_id,
yield counter.Counter( resource_id=ip.id,
name='ip.floating', timestamp=timeutils.utcnow().isoformat(),
type=counter.TYPE_GAUGE, resource_metadata={
volume=1, 'address': ip.address,
user_id=None, 'fixed_ip_id': ip.fixed_ip_id,
project_id=ip.project_id, 'host': ip.host,
resource_id=ip.id, 'pool': ip.pool,
timestamp=timeutils.utcnow().isoformat(), 'auto_assigned': ip.auto_assigned
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): 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', 'reservation_id': 'reservation id',
'architecture': 'x86_64', 'architecture': 'x86_64',
'availability_zone': 'zone1', 'availability_zone': 'zone1',
@ -58,6 +60,8 @@ class TestLocationMetadata(unittest.TestCase):
'memory_mb': 2048, 'memory_mb': 2048,
'root_gb': 3, 'root_gb': 3,
'vcpus': 1, 'vcpus': 1,
'flavor': {'id': 1},
'hostId': '1234-5678'
} }
def setUp(self): def setUp(self):
@ -70,9 +74,18 @@ class TestLocationMetadata(unittest.TestCase):
self.instance.instance_type = m self.instance.instance_type = m
def test_metadata(self): def test_metadata(self):
md = instance.get_metadata_from_dbobject(self.instance) md = instance.get_metadata_from_object(self.instance)
for name in self.INSTANCE_PROPERTIES.keys(): iprops = self.INSTANCE_PROPERTIES
for name in md.keys():
actual = md[name] actual = md[name]
print 'checking', name, actual print 'checking', name, actual
expected = self.INSTANCE_PROPERTIES[name] if name == 'name':
assert actual == expected 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.manager = manager.AgentManager()
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',
self.instance.name)
self.instance.id = 1 self.instance.id = 1
self.instance.instance_type = mock.MagicMock() self.instance.instance_type = mock.MagicMock()
self.instance.instance_type.name = 'm1.small' self.instance.instance_type.name = 'm1.small'
@ -217,10 +219,13 @@ class TestCPUPollster(TestLibvirtBase):
self.instance.vcpus = 1 self.instance.vcpus = 1
conn = fake_libvirt_conn(self.mox, 3) conn = fake_libvirt_conn(self.mox, 3)
self.mox.StubOutWithMock(conn, 'get_info') self.mox.StubOutWithMock(conn, 'get_info')
conn.get_info(self.instance).AndReturn({'cpu_time': 1 * (10 ** 6)}) conn.get_info({'name': self.instance.name}).AndReturn(
conn.get_info(self.instance).AndReturn({'cpu_time': 3 * (10 ** 6)}) {'cpu_time': 1 * (10 ** 6)})
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 3 * (10 ** 6)})
# cpu_time resets on instance restart # 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() self.mox.ReplayAll()
def _verify_cpu_metering(zero, expected_time): def _verify_cpu_metering(zero, expected_time):

View File

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

View File

@ -19,8 +19,7 @@
import mock import mock
from nova import db from ceilometer import nova_client
from ceilometer.network import floatingip from ceilometer.network import floatingip
from ceilometer.central import manager from ceilometer.central import manager
from ceilometer.openstack.common import context from ceilometer.openstack.common import context
@ -34,9 +33,10 @@ class TestFloatingIPPollster(base.TestCase):
self.context = context.get_admin_context() self.context = context.get_admin_context()
self.manager = manager.AgentManager() self.manager = manager.AgentManager()
self.pollster = floatingip.FloatingIPPollster() 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 = [] ips = []
for i in range(1, 4): for i in range(1, 4):
ip = mock.MagicMock() ip = mock.MagicMock()

View File

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