Virt inspector directly layered over hypervisor API

Addresses BP nova-independent-virt

Add the concept of a pluggable virt inspector that allows
polling of the hypervisor layer without relying on any
nova internals.

Note that the test-requires dependency on the nova master
tarball remains in place for the moment, as it's required
by the nova notifier tests.

Also we must leave the dependency on the libvirt-python
RPM or python-libvirt Debian package to the distros which
end up packaging ceilometer.

Change-Id: I20700320dd7e3196507173c780ab598b479e4021
This commit is contained in:
Eoghan Glynn 2012-12-04 12:53:11 +00:00
parent b12376fed9
commit 40a3874c38
13 changed files with 678 additions and 363 deletions

View File

@ -1,8 +1,10 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 eNovance <licensing@enovance.com>
# Copyright © 2012 Red Hat, Inc
#
# Author: Julien Danjou <julien@danjou.info>
# Author: Eoghan Glynn <eglynn@redhat.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,58 +21,44 @@
import copy
import datetime
from lxml import etree
from stevedore import driver
try:
from nova import config as nova_config
except ImportError:
# NOTE(dhellmann): We want to try to maintain compatibility
# with folsom for the time being, so set the name nova_config
# to a sentinal we can use to trigger different behavior
# when we try to set up the configuration object.
from nova import flags
nova_config = False
# Import this to register compute_driver flag in Folsom
import nova.compute.manager
from nova.virt import driver
from ceilometer import counter
from ceilometer.compute import plugin
from ceilometer.compute import instance as compute_instance
from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.openstack.common import cfg
from ceilometer.openstack.common import importutils
from ceilometer.openstack.common import log
from ceilometer.openstack.common import timeutils
OPTS = [
cfg.StrOpt('hypervisor_inspector',
help='Inspector to use for inspecting the hypervisor layer',
default='libvirt')
]
CONF = cfg.CONF
CONF.register_opts(OPTS)
LOG = log.getLogger(__name__)
def _instance_name(instance):
"""Shortcut to get instance name"""
return getattr(instance, 'OS-EXT-SRV-ATTR:instance_name', None)
def get_compute_driver():
# FIXME(jd) This function is made to be destroyed by an abstraction
# layer in Nova providing an hypervisor agnostic API.
# XXX(jd) Folsom compat
if not nova_config:
flags.parse_args([])
return flags.FLAGS.compute_driver
nova_config.parse_args([])
return nova_config.cfg.CONF.compute_driver or ""
def get_libvirt_connection():
"""Return an open connection for talking to libvirt."""
# The direct-import implementation only works with Folsom because
# the configuration setting changed.
def get_hypervisor_inspector():
try:
try:
return driver.load_compute_driver(None)
except AttributeError:
return importutils.import_object_ns('nova.virt',
get_compute_driver())
except ImportError:
# Fall back to the way it was done in Essex.
import nova.virt.connection
return nova.virt.connection.get_connection(read_only=True)
namespace = 'ceilometer.compute.virt'
mgr = driver.DriverManager(namespace,
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()
def make_counter_from_instance(instance, name, type, volume):
@ -86,18 +74,16 @@ def make_counter_from_instance(instance, name, type, volume):
)
class LibVirtPollster(plugin.ComputePollster):
class HypervisorPollster(plugin.ComputePollster):
def is_enabled(self):
# XXX(llu): Keeps Folsom compatibility
try:
return driver.compute_driver_matches("LibvirtDriver")
except AttributeError:
# Use a fairly liberal substring check.
return 'libvirt' in get_compute_driver().lower()
inspector = None
def __init__(self):
if not HypervisorPollster.inspector:
HypervisorPollster.inspector = get_hypervisor_inspector()
class InstancePollster(LibVirtPollster):
class InstancePollster(HypervisorPollster):
def get_counters(self, manager, instance):
yield make_counter_from_instance(instance,
@ -113,7 +99,7 @@ class InstancePollster(LibVirtPollster):
)
class DiskIOPollster(LibVirtPollster):
class DiskIOPollster(HypervisorPollster):
LOG = log.getLogger(__name__ + '.diskio')
@ -127,28 +113,21 @@ class DiskIOPollster(LibVirtPollster):
])
def get_counters(self, manager, instance):
conn = get_libvirt_connection()
instance_name = _instance_name(instance)
try:
disks = conn.get_disks(instance_name)
except Exception as err:
self.LOG.warning('Ignoring instance %s: %s',
instance_name, err)
self.LOG.exception(err)
else:
r_bytes = 0
r_requests = 0
w_bytes = 0
w_requests = 0
for disk in disks:
stats = conn.block_stats(instance_name, disk)
for disk, info in self.inspector.inspect_disks(instance_name):
self.LOG.info(self.DISKIO_USAGE_MESSAGE,
instance, disk, stats[0], stats[1],
stats[2], stats[3], stats[4])
r_bytes += stats[0]
r_requests += stats[1]
w_bytes += stats[3]
w_requests += stats[2]
instance, disk.device, info.read_requests,
info.read_bytes, info.write_requests,
info.write_bytes, info.errors)
r_bytes += info.read_bytes
r_requests += info.read_requests
w_bytes += info.write_bytes
w_requests += info.write_requests
yield make_counter_from_instance(instance,
name='disk.read.requests',
type=counter.TYPE_CUMULATIVE,
@ -169,9 +148,13 @@ class DiskIOPollster(LibVirtPollster):
type=counter.TYPE_CUMULATIVE,
volume=w_bytes,
)
except Exception as err:
self.LOG.warning('Ignoring instance %s: %s',
instance_name, err)
self.LOG.exception(err)
class CPUPollster(LibVirtPollster):
class CPUPollster(HypervisorPollster):
LOG = log.getLogger(__name__ + '.cpu')
@ -179,7 +162,7 @@ class CPUPollster(LibVirtPollster):
def get_cpu_util(self, instance, cpu_info):
prev_times = self.utilization_map.get(instance.id)
self.utilization_map[instance.id] = (cpu_info['cpu_time'],
self.utilization_map[instance.id] = (cpu_info.time,
datetime.datetime.now())
cpu_util = 0.0
if prev_times:
@ -187,21 +170,20 @@ class CPUPollster(LibVirtPollster):
prev_timestamp = prev_times[1]
delta = self.utilization_map[instance.id][1] - prev_timestamp
elapsed = (delta.seconds * (10 ** 6) + delta.microseconds) * 1000
cores_fraction = 1.0 / cpu_info['num_cpu']
cores_fraction = 1.0 / cpu_info.number
# account for cpu_time being reset when the instance is restarted
time_used = (cpu_info['cpu_time'] - prev_cpu
if prev_cpu <= cpu_info['cpu_time'] else
cpu_info['cpu_time'])
time_used = (cpu_info.time - prev_cpu
if prev_cpu <= cpu_info.time else cpu_info.time)
cpu_util = 100 * cores_fraction * time_used / elapsed
return cpu_util
def get_counters(self, manager, instance):
conn = get_libvirt_connection()
self.LOG.info('checking instance %s', instance.id)
instance_name = _instance_name(instance)
try:
cpu_info = conn.get_info({'name': _instance_name(instance)})
cpu_info = self.inspector.inspect_cpus(instance_name)
self.LOG.info("CPUTIME USAGE: %s %d",
instance.__dict__, cpu_info['cpu_time'])
instance.__dict__, cpu_info.time)
cpu_util = self.get_cpu_util(instance, cpu_info)
self.LOG.info("CPU UTILIZATION %%: %s %0.2f",
instance.__dict__, cpu_util)
@ -218,7 +200,7 @@ class CPUPollster(LibVirtPollster):
yield make_counter_from_instance(instance,
name='cpu',
type=counter.TYPE_CUMULATIVE,
volume=cpu_info['cpu_time'],
volume=cpu_info.time,
)
except Exception as err:
self.LOG.error('could not get CPU time for %s: %s',
@ -226,31 +208,17 @@ class CPUPollster(LibVirtPollster):
self.LOG.exception(err)
class NetPollster(LibVirtPollster):
class NetPollster(HypervisorPollster):
LOG = log.getLogger(__name__ + '.net')
NET_USAGE_MESSAGE = ' '.join(["NETWORK USAGE:", "%s %s:", "read-bytes=%d",
"write-bytes=%d"])
def _get_vnics(self, conn, instance):
"""Get disks of an instance, only used to bypass bug#998089."""
domain = conn._conn.lookupByName(_instance_name(instance))
tree = etree.fromstring(domain.XMLDesc(0))
vnics = []
for interface in tree.findall('devices/interface'):
vnic = {}
vnic['name'] = interface.find('target').get('dev')
vnic['mac'] = interface.find('mac').get('address')
vnic['fref'] = interface.find('filterref').get('filter')
for param in interface.findall('filterref/parameter'):
vnic[param.get('name').lower()] = param.get('value')
vnics.append(vnic)
return vnics
@staticmethod
def make_vnic_counter(instance, name, type, volume, vnic_data):
resource_metadata = copy.copy(vnic_data)
metadata = copy.copy(vnic_data)
resource_metadata = dict(zip(metadata._fields, metadata))
resource_metadata['instance_id'] = instance.id
return counter.Counter(
@ -259,50 +227,43 @@ class NetPollster(LibVirtPollster):
volume=volume,
user_id=instance.user_id,
project_id=instance.tenant_id,
resource_id=vnic_data['fref'],
resource_id=vnic_data.fref,
timestamp=timeutils.isotime(),
resource_metadata=resource_metadata
)
def get_counters(self, manager, instance):
conn = get_libvirt_connection()
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)
self.LOG.exception(err)
else:
domain = conn._conn.lookupByName(instance_name)
for vnic in vnics:
rx_bytes, rx_packets, _, _, \
tx_bytes, tx_packets, _, _ = \
domain.interfaceStats(vnic['name'])
for vnic, info in self.inspector.inspect_vnics(instance_name):
self.LOG.info(self.NET_USAGE_MESSAGE, instance_name,
vnic['name'], rx_bytes, tx_bytes)
vnic.name, info.rx_bytes, info.tx_bytes)
yield self.make_vnic_counter(instance,
name='network.incoming.bytes',
type=counter.TYPE_CUMULATIVE,
volume=rx_bytes,
volume=info.rx_bytes,
vnic_data=vnic,
)
yield self.make_vnic_counter(instance,
name='network.outgoing.bytes',
type=counter.TYPE_CUMULATIVE,
volume=tx_bytes,
volume=info.tx_bytes,
vnic_data=vnic,
)
yield self.make_vnic_counter(instance,
name='network.incoming.packets',
type=counter.TYPE_CUMULATIVE,
volume=rx_packets,
volume=info.rx_packets,
vnic_data=vnic,
)
yield self.make_vnic_counter(instance,
name='network.outgoing.packets',
type=counter.TYPE_CUMULATIVE,
volume=tx_packets,
volume=info.tx_packets,
vnic_data=vnic,
)
except Exception as err:
self.LOG.warning('Ignoring instance %s: %s',
instance_name, err)
self.LOG.exception(err)

View File

View File

@ -0,0 +1,130 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc
#
# Author: Eoghan Glynn <eglynn@redhat.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.
"""Inspector abstraction for read-only access to hypervisors"""
import collections
# Named tuple representing instances.
#
# name: the name of the instance
# uuid: the UUID associated with the instance
#
Instance = collections.namedtuple('Instance', ['name', 'UUID'])
# Named tuple representing CPU statistics.
#
# number: number of CPUs
# time: cumulative CPU time
#
CPUStats = collections.namedtuple('CPUStats', ['number', 'time'])
# Named tuple representing vNICs.
#
# name: the name of the vNIC
# mac: the MAC address
# fref: the filter ref
# parameters: miscellaneous parameters
#
Interface = collections.namedtuple('Interface', ['name', 'mac',
'fref', 'parameters'])
# Named tuple representing vNIC statistics.
#
# rx_bytes: number of received bytes
# rx_packets: number of received packets
# tx_bytes: number of transmitted bytes
# tx_packets: number of transmitted packets
#
InterfaceStats = collections.namedtuple('InterfaceStats',
['rx_bytes', 'rx_packets',
'tx_bytes', 'tx_packets'])
# Named tuple representing disks.
#
# device: the device name for the disk
#
Disk = collections.namedtuple('Disk', ['device'])
# Named tuple representing disk statistics.
#
# read_bytes: number of bytes read
# read_requests: number of read operations
# write_bytes: number of bytes written
# write_requests: number of write operations
# errors: number of errors
#
DiskStats = collections.namedtuple('DiskStats',
['read_bytes', 'read_requests',
'write_bytes', 'write_requests',
'errors'])
# Exception types
#
class InspectorException(Exception):
def __init__(self, message=None):
super(InspectorException, self).__init__(message)
class InstanceNotFoundException(InspectorException):
pass
# Main virt inspector abstraction layering over the hypervisor API.
#
class Inspector(object):
def inspect_instances(self):
"""
List the instances on the current host.
"""
raise NotImplementedError()
def inspect_cpus(self, instance_name):
"""
Inspect the CPU statistics for an instance.
:param instance_name: the name of the target instance
:return: the number of CPUs and cumulative CPU time
"""
raise NotImplementedError()
def inspect_vnics(self, instance_name):
"""
Inspect the vNIC statistics for an instance.
:param instance_name: the name of the target instance
:return: for each vNIC, the number of bytes & packets
received and transmitted
"""
raise NotImplementedError()
def inspect_disks(self, instance_name):
"""
Inspect the disk statistics for an instance.
:param instance_name: the name of the target instance
:return: for each disk, the number of bytes & operations
read and written, and the error count
"""
raise NotImplementedError()

View File

@ -0,0 +1,139 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc
#
# Author: Eoghan Glynn <eglynn@redhat.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.
"""Implementation of Inspector abstraction for libvirt"""
from lxml import etree
from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.openstack.common import cfg
from ceilometer.openstack.common import log as logging
libvirt = None
LOG = logging.getLogger(__name__)
libvirt_opts = [
cfg.StrOpt('libvirt_type',
default='kvm',
help='Libvirt domain type (valid options are: '
'kvm, lxc, qemu, uml, xen)'),
cfg.StrOpt('libvirt_uri',
default='',
help='Override the default libvirt URI '
'(which is dependent on libvirt_type)'),
]
CONF = cfg.CONF
CONF.register_opts(libvirt_opts)
class LibvirtInspector(virt_inspector.Inspector):
per_type_uris = dict(uml='uml:///system', xen='xen:///', lxc='lxc:///')
def __init__(self):
self.uri = self._get_uri()
self.connection = None
def _get_uri(self):
return CONF.libvirt_uri or self.per_type_uris.get(CONF.libvirt_type,
'qemu:///system')
def _get_connection(self):
if not self.connection or not self._test_connection():
global libvirt
if libvirt is None:
libvirt = __import__('libvirt')
LOG.debug('Connecting to libvirt: %s', self.uri)
self.connection = libvirt.openReadOnly(self.uri)
return self.connection
def _test_connection(self):
try:
self.connection.getCapabilities()
return True
except libvirt.libvirtError as e:
if (e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and
e.get_error_domain() in (libvirt.VIR_FROM_REMOTE,
libvirt.VIR_FROM_RPC)):
LOG.debug('Connection to libvirt broke')
return False
raise
def _lookup_by_name(self, instance_name):
try:
return self._get_connection().lookupByName(instance_name)
except Exception as ex:
error_code = ex.get_error_code() if libvirt else 'unknown'
msg = ("Error from libvirt while looking up %(instance_name)s: "
"[Error Code %(error_code)s] %(ex)s" % locals())
raise virt_inspector.InstanceNotFoundException(msg)
def inspect_instances(self):
if self._get_connection().numOfDomains() > 0:
for domain_id in self._get_connection().listDomainsID():
try:
# We skip domains with ID 0 (hypervisors).
if domain_id != 0:
domain = self._get_connection().lookupByID(domain_id)
yield virt_inspector.Instance(name=domain.name(),
uuid=domain.UUIDString())
except libvirt.libvirtError:
# Instance was deleted while listing... ignore it
pass
def inspect_cpus(self, instance_name):
domain = self._lookup_by_name(instance_name)
(_, _, _, num_cpu, cpu_time) = domain.info()
return virt_inspector.CPUStats(number=num_cpu, time=cpu_time)
def inspect_vnics(self, instance_name):
domain = self._lookup_by_name(instance_name)
tree = etree.fromstring(domain.XMLDesc(0))
for iface in tree.findall('devices/interface'):
name = iface.find('target').get('dev')
mac = iface.find('mac').get('address')
fref = iface.find('filterref').get('filter')
params = dict((p.get('name').lower(), p.get('value'))
for p in iface.findall('filterref/parameter'))
interface = virt_inspector.Interface(name=name, mac=mac,
fref=fref, parameters=params)
rx_bytes, rx_packets, _, _, \
tx_bytes, tx_packets, _, _ = domain.interfaceStats(name)
stats = virt_inspector.InterfaceStats(rx_bytes=rx_bytes,
rx_packets=rx_packets,
tx_bytes=tx_bytes,
tx_packets=tx_packets)
yield (interface, stats)
def inspect_disks(self, instance_name):
domain = self._lookup_by_name(instance_name)
tree = etree.fromstring(domain.XMLDesc(0))
for device in filter(bool,
[target.get("dev")
for target in tree.findall('devices/disk/target')]):
disk = virt_inspector.Disk(device=device)
block_stats = domain.blockStats(device)
stats = virt_inspector.DiskStats(read_requests=block_stats[0],
read_bytes=block_stats[1],
write_requests=block_stats[2],
write_bytes=block_stats[3],
errors=block_stats[4])
yield (disk, stats)

View File

@ -51,8 +51,8 @@ information and send them to the collector. As stated above, an agent
will automatically activate all plugins of a given class. For example,
the compute agent will load all plugins of class
``ceilometer.poll.compute``. This will load, among others, the
:class:`ceilometer.compute.libvirt.CPUPollster`, which is defined in
the file ``ceilometer/compute/libvirt.py`` as well as the
:class:`ceilometer.compute.pollsters.CPUPollster`, which is defined in
the file ``ceilometer/compute/pollsters.py`` as well as the
:class:`ceilometer.compute.notifications.InstanceNotifications` plugin
which is defined in the file ``ceilometer/compute/notifications.py``
@ -73,7 +73,7 @@ sequence of ``Counter`` objects as defined in the
In the ``CPUPollster`` plugin, the ``get_counters`` method is implemented as a loop
which, for each instances running on the local host, retrieves the cpu_time
from libvirt and send back two ``Counter`` objects. The first one, named
from the hypervisor and sends back two ``Counter`` objects. The first one, named
"cpu", is of type "cumulative", meaning that between two polls, its value is
not reset, or in other word that the cpu value is always provided as a duration
that continuously increases since the creation of the instance. The second one,

View File

@ -111,10 +111,10 @@ setuptools.setup(
floatingip = ceilometer.network.notifications:FloatingIP
[ceilometer.poll.compute]
libvirt_diskio = ceilometer.compute.libvirt:DiskIOPollster
libvirt_cpu = ceilometer.compute.libvirt:CPUPollster
libvirt_net = ceilometer.compute.libvirt:NetPollster
libvirt_instance = ceilometer.compute.libvirt:InstancePollster
diskio = ceilometer.compute.pollsters:DiskIOPollster
cpu = ceilometer.compute.pollsters:CPUPollster
net = ceilometer.compute.pollsters:NetPollster
instance = ceilometer.compute.pollsters:InstancePollster
[ceilometer.poll.central]
network_floatingip = ceilometer.network.floatingip:FloatingIPPollster
@ -129,5 +129,8 @@ setuptools.setup(
postgresql = ceilometer.storage.impl_sqlalchemy:SQLAlchemyStorage
sqlite = ceilometer.storage.impl_sqlalchemy:SQLAlchemyStorage
test = ceilometer.storage.impl_test:TestDBStorage
[ceilometer.compute.virt]
libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector
"""),
)

View File

@ -1,249 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 eNovance <licensing@enovance.com>
#
# Author: Julien Danjou <julien@danjou.info>
#
# 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 manager.
"""
try:
import libvirt as ignored_libvirt
except ImportError:
libvirt_missing = True
else:
libvirt_missing = False
import mock
import time
try:
from nova import config
nova_CONF = config.cfg.CONF
except ImportError:
# XXX Folsom compat
from nova import flags
nova_CONF = flags.FLAGS
from ceilometer.compute import libvirt
from ceilometer.compute import manager
from ceilometer.tests import base as test_base
from ceilometer.tests import skip
import mox
import re
def fake_libvirt_conn(moxobj, count=1):
conn = moxobj.CreateMockAnything()
conn._conn = moxobj.CreateMockAnything()
moxobj.StubOutWithMock(libvirt, 'get_libvirt_connection')
for _ in xrange(count):
libvirt.get_libvirt_connection().AndReturn(conn)
return conn
class TestLibvirtBase(test_base.TestCase):
def setUp(self):
super(TestLibvirtBase, self).setUp()
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.flavor = {'name': 'm1.small', 'id': 2}
nova_CONF.compute_driver = 'libvirt.LibvirtDriver'
nova_CONF.connection_type = 'libvirt'
class TestInstancePollster(TestLibvirtBase):
@skip.skip_if(libvirt_missing, 'Test requires libvirt')
def setUp(self):
super(TestInstancePollster, self).setUp()
self.pollster = libvirt.InstancePollster()
def test_get_counter(self):
counters = list(self.pollster.get_counters(self.manager,
self.instance))
self.assertEquals(len(counters), 2)
self.assertEqual(counters[0].name, 'instance')
self.assertEqual(counters[1].name, 'instance:m1.small')
class TestDiskIOPollster(TestLibvirtBase):
def setUp(self):
super(TestDiskIOPollster, self).setUp()
self.pollster = libvirt.DiskIOPollster()
@skip.skip_if(libvirt_missing, 'Test requires libvirt')
def test_fetch_diskio(self):
nova_CONF.compute_driver = 'fake.FakeDriver'
list(self.pollster.get_counters(self.manager, self.instance))
#assert counters
# FIXME(dhellmann): The CI environment doesn't produce
# a response when the fake driver asks for the disks, so
# we do not get any counters in response.
@skip.skip_if(libvirt_missing, 'Test requires libvirt')
def test_fetch_diskio_not_libvirt(self):
nova_CONF.compute_driver = 'fake.FakeDriver'
nova_CONF.connection_type = 'fake'
counters = list(self.pollster.get_counters(self.manager,
self.instance))
assert not counters
@skip.skip_if(libvirt_missing, 'Test requires libvirt')
def test_fetch_diskio_with_libvirt_non_existent_instance(self):
nova_CONF.compute_driver = 'fake.FakeDriver'
instance = mock.MagicMock()
instance.name = 'instance-00000999'
instance.id = 999
counters = list(self.pollster.get_counters(self.manager, instance))
assert not counters
class TestNetPollster(TestLibvirtBase):
def setUp(self):
super(TestNetPollster, self).setUp()
self.pollster = libvirt.NetPollster()
def test_get_vnics(self):
dom_xml = """
<domain type='kvm'>
<devices>
<interface type='bridge'>
<mac address='fa:16:3e:71:ec:6d'/>
<source bridge='br100'/>
<target dev='vnet0'/>
<filterref filter=
'nova-instance-instance-00000001-fa163e71ec6d'>
<parameter name='DHCPSERVER' value='10.0.0.1'/>
<parameter name='IP' value='10.0.0.2'/>
<parameter name='PROJMASK' value='255.255.255.0'/>
<parameter name='PROJNET' value='10.0.0.0'/>
</filterref>
<alias name='net0'/>
</interface>
<interface type='bridge'>
<mac address='fa:16:3e:71:ec:6e'/>
<source bridge='br100'/>
<target dev='vnet1'/>
<filterref filter=
'nova-instance-instance-00000001-fa163e71ec6e'>
<parameter name='DHCPSERVER' value='192.168.0.1'/>
<parameter name='IP' value='192.168.0.2'/>
<parameter name='PROJMASK' value='255.255.255.0'/>
<parameter name='PROJNET' value='192.168.0.0'/>
</filterref>
<alias name='net1'/>
</interface>
</devices>
</domain>
"""
ignore = mox.IgnoreArg()
conn = self.mox.CreateMockAnything()
domain = self.mox.CreateMockAnything()
conn._conn = self.mox.CreateMockAnything()
self.mox.StubOutWithMock(conn._conn, 'lookupByName')
conn._conn.lookupByName(self.instance.name).AndReturn(domain)
self.mox.StubOutWithMock(domain, 'XMLDesc')
domain.XMLDesc(0).AndReturn(dom_xml)
self.mox.ReplayAll()
interfaces = self.pollster._get_vnics(conn, self.instance)
self.assertTrue('vnet1' in [x['name'] for x in interfaces])
self.assertTrue('fa:16:3e:71:ec:6d', [x['mac'] for x in interfaces])
self.assertTrue([x['dhcpserver'] for x in interfaces])
def test_get_counters(self):
interface_stats1 = (3876L, 15L, 0L, 0L, 15830L, 0L, 0L, 0L)
interface_stats2 = (9999L, 99L, 0L, 0L, 88888L, 0L, 0L, 0L)
vnics = [
{'name': 'vnet0',
'ip': '10.0.0.2',
'projmask': '255.255.255.0',
'projnet': 'proj1',
'fref': 'nova-instance-instance-00000001-fa163e71ec6e',
'bridge': 'br100',
'dhcp_server': '10.0.0.1',
'alias': 'net0',
'mac': 'fa:16:3e:71:ec:6d'},
{'name': 'vnet1',
'ip': '192.168.0.3',
'projmask': '255.255.255.0',
'projnet': 'proj2',
'fref': 'nova-instance-instance-00000001-fa163e71ec6f',
'bridge': 'br100',
'dhcp_server': '192.168.0.1',
'fref': '00:00:00:01:1e',
'alias': 'net1',
'mac': 'fa:16:3e:71:ec:6e'}
]
conn = fake_libvirt_conn(self.mox)
ignore = mox.IgnoreArg()
domain = self.mox.CreateMockAnything()
self.mox.StubOutWithMock(self.pollster, '_get_vnics')
self.pollster._get_vnics(ignore, ignore).AndReturn(vnics)
self.mox.StubOutWithMock(conn._conn, 'lookupByName')
conn._conn.lookupByName(self.instance.name).AndReturn(domain)
self.mox.StubOutWithMock(domain, 'interfaceStats')
domain.interfaceStats('vnet0').AndReturn(interface_stats1)
domain.interfaceStats('vnet1').AndReturn(interface_stats2)
self.mox.ReplayAll()
results = list(self.pollster.get_counters(self.manager, self.instance))
self.assertTrue([countr.resource_metadata['ip'] for countr in results])
self.assertTrue([countr.resource_id for countr in results])
class TestCPUPollster(TestLibvirtBase):
def setUp(self):
super(TestCPUPollster, self).setUp()
self.pollster = libvirt.CPUPollster()
def test_get_counter(self):
conn = fake_libvirt_conn(self.mox, 3)
self.mox.StubOutWithMock(conn, 'get_info')
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 1 * (10 ** 6), 'num_cpu': 2})
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 3 * (10 ** 6), 'num_cpu': 2})
# cpu_time resets on instance restart
conn.get_info({'name': self.instance.name}).AndReturn(
{'cpu_time': 2 * (10 ** 6), 'num_cpu': 2})
self.mox.ReplayAll()
def _verify_cpu_metering(zero, expected_time):
counters = list(self.pollster.get_counters(self.manager,
self.instance))
self.assertEquals(len(counters), 2)
assert counters[0].name == 'cpu_util'
assert (counters[0].volume == 0.0 if zero else
counters[0].volume > 0.0)
assert counters[1].name == 'cpu'
assert counters[1].volume == expected_time
# ensure elapsed time between polling cycles is non-zero
time.sleep(0.001)
_verify_cpu_metering(True, 1 * (10 ** 6))
_verify_cpu_metering(False, 3 * (10 ** 6))
_verify_cpu_metering(False, 2 * (10 ** 6))

View File

@ -0,0 +1,179 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 eNovance <licensing@enovance.com>
# Copyright © 2012 Red Hat, Inc
#
# Author: Julien Danjou <julien@danjou.info>
# Author: Eoghan Glynn <eglynn@redhat.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 the compute pollsters.
"""
import mock
import time
from ceilometer.compute import pollsters
from ceilometer.compute import manager
from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.tests import base as test_base
import mox
class TestPollsterBase(test_base.TestCase):
def setUp(self):
super(TestPollsterBase, self).setUp()
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.flavor = {'name': 'm1.small', 'id': 2}
self.mox.StubOutWithMock(pollsters, 'get_hypervisor_inspector')
self.inspector = self.mox.CreateMock(virt_inspector.Inspector)
pollsters.get_hypervisor_inspector().AndReturn(self.inspector)
pollsters.HypervisorPollster.inspector = None
class TestInstancePollster(TestPollsterBase):
def setUp(self):
super(TestInstancePollster, self).setUp()
def test_get_counters(self):
self.mox.ReplayAll()
pollster = pollsters.InstancePollster()
counters = list(pollster.get_counters(self.manager,
self.instance))
self.assertEquals(len(counters), 2)
self.assertEqual(counters[0].name, 'instance')
self.assertEqual(counters[1].name, 'instance:m1.small')
class TestDiskIOPollster(TestPollsterBase):
def setUp(self):
super(TestDiskIOPollster, self).setUp()
def test_get_counters(self):
disks = [
(virt_inspector.Disk(device='vda'),
virt_inspector.DiskStats(read_bytes=1L, read_requests=2L,
write_bytes=3L, write_requests=4L,
errors=-1L))
]
self.inspector.inspect_disks(self.instance.name).AndReturn(disks)
self.mox.ReplayAll()
pollster = pollsters.DiskIOPollster()
counters = list(pollster.get_counters(self.manager, self.instance))
assert counters
def _verify_disk_metering(name, expected_volume):
match = [c for c in counters if c.name == name]
self.assertEquals(len(match), 1, 'missing counter %s' % name)
self.assertEquals(match[0].volume, expected_volume)
self.assertEquals(match[0].type, 'cumulative')
_verify_disk_metering('disk.read.requests', 2L)
_verify_disk_metering('disk.read.bytes', 1L)
_verify_disk_metering('disk.write.requests', 4L)
_verify_disk_metering('disk.write.bytes', 3L)
class TestNetPollster(TestPollsterBase):
def setUp(self):
super(TestNetPollster, self).setUp()
def test_get_counters(self):
vnic0 = virt_inspector.Interface(name='vnet0',
fref='fa163e71ec6e',
mac='fa:16:3e:71:ec:6d',
parameters=dict(ip='10.0.0.2',
projmask='255.255.255.0',
projnet='proj1',
dhcp_server='10.0.0.1'))
stats0 = virt_inspector.InterfaceStats(rx_bytes=1L, rx_packets=2L,
tx_bytes=3L, tx_packets=4L)
vnic1 = virt_inspector.Interface(name='vnet1',
fref='fa163e71ec6f',
mac='fa:16:3e:71:ec:6e',
parameters=dict(ip='192.168.0.3',
projmask='255.255.255.0',
projnet='proj2',
dhcp_server='10.0.0.2'))
stats1 = virt_inspector.InterfaceStats(rx_bytes=5L, rx_packets=6L,
tx_bytes=7L, tx_packets=8L)
vnics = [(vnic0, stats0), (vnic1, stats1)]
self.inspector.inspect_vnics(self.instance.name).AndReturn(vnics)
self.mox.ReplayAll()
pollster = pollsters.NetPollster()
counters = list(pollster.get_counters(self.manager, self.instance))
assert counters
def _verify_vnic_metering(name, ip, expected_volume):
match = [c for c in counters if c.name == name and
c.resource_metadata['parameters']['ip'] == ip]
self.assertEquals(len(match), 1, 'missing counter %s' % name)
self.assertEquals(match[0].volume, expected_volume)
self.assertEquals(match[0].type, 'cumulative')
_verify_vnic_metering('network.incoming.bytes', '10.0.0.2', 1L)
_verify_vnic_metering('network.incoming.bytes', '192.168.0.3', 5L)
_verify_vnic_metering('network.outgoing.bytes', '10.0.0.2', 3L)
_verify_vnic_metering('network.outgoing.bytes', '192.168.0.3', 7L)
_verify_vnic_metering('network.incoming.packets', '10.0.0.2', 2L)
_verify_vnic_metering('network.incoming.packets', '192.168.0.3', 6L)
_verify_vnic_metering('network.outgoing.packets', '10.0.0.2', 4L)
_verify_vnic_metering('network.outgoing.packets', '192.168.0.3', 8L)
class TestCPUPollster(TestPollsterBase):
def setUp(self):
super(TestCPUPollster, self).setUp()
def test_get_counters(self):
self.inspector.inspect_cpus(self.instance.name).AndReturn(
virt_inspector.CPUStats(time=1 * (10 ** 6), number=2))
self.inspector.inspect_cpus(self.instance.name).AndReturn(
virt_inspector.CPUStats(time=3 * (10 ** 6), number=2))
# cpu_time resets on instance restart
self.inspector.inspect_cpus(self.instance.name).AndReturn(
virt_inspector.CPUStats(time=2 * (10 ** 6), number=2))
self.mox.ReplayAll()
pollster = pollsters.CPUPollster()
def _verify_cpu_metering(zero, expected_time):
counters = list(pollster.get_counters(self.manager,
self.instance))
self.assertEquals(len(counters), 2)
assert counters[0].name == 'cpu_util'
assert (counters[0].volume == 0.0 if zero else
counters[0].volume > 0.0)
assert counters[1].name == 'cpu'
assert counters[1].volume == expected_time
# ensure elapsed time between polling cycles is non-zero
time.sleep(0.001)
_verify_cpu_metering(True, 1 * (10 ** 6))
_verify_cpu_metering(False, 3 * (10 ** 6))
_verify_cpu_metering(False, 2 * (10 ** 6))

View File

View File

View File

@ -0,0 +1,151 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc
#
# Author: Eoghan Glynn <eglynn@redhat.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 libvirt inspector.
"""
import time
from ceilometer.compute.virt.libvirt import inspector as libvirt_inspector
from ceilometer.tests import base as test_base
import mox
class TestLibvirtInspection(test_base.TestCase):
def setUp(self):
super(TestLibvirtInspection, self).setUp()
self.instance_name = 'instance-00000001'
self.inspector = libvirt_inspector.LibvirtInspector()
self.inspector.connection = self.mox.CreateMockAnything()
self.inspector.connection.getCapabilities()
self.domain = self.mox.CreateMockAnything()
self.inspector.connection.lookupByName(self.instance_name).AndReturn(
self.domain)
def test_inspect_cpus(self):
self.domain.info().AndReturn((0L, 0L, 0L, 2L, 999999L))
self.mox.ReplayAll()
cpu_info = self.inspector.inspect_cpus(self.instance_name)
self.assertEqual(cpu_info.number, 2L)
self.assertEqual(cpu_info.time, 999999L)
def test_inspect_vnics(self):
dom_xml = """
<domain type='kvm'>
<devices>
<interface type='bridge'>
<mac address='fa:16:3e:71:ec:6d'/>
<source bridge='br100'/>
<target dev='vnet0'/>
<filterref filter=
'nova-instance-00000001-fa163e71ec6d'>
<parameter name='DHCPSERVER' value='10.0.0.1'/>
<parameter name='IP' value='10.0.0.2'/>
<parameter name='PROJMASK' value='255.255.255.0'/>
<parameter name='PROJNET' value='10.0.0.0'/>
</filterref>
<alias name='net0'/>
</interface>
<interface type='bridge'>
<mac address='fa:16:3e:71:ec:6e'/>
<source bridge='br100'/>
<target dev='vnet1'/>
<filterref filter=
'nova-instance-00000001-fa163e71ec6e'>
<parameter name='DHCPSERVER' value='192.168.0.1'/>
<parameter name='IP' value='192.168.0.2'/>
<parameter name='PROJMASK' value='255.255.255.0'/>
<parameter name='PROJNET' value='192.168.0.0'/>
</filterref>
<alias name='net1'/>
</interface>
</devices>
</domain>
"""
self.domain.XMLDesc(0).AndReturn(dom_xml)
self.domain.interfaceStats('vnet0').AndReturn((1L, 2L, 0L, 0L,
3L, 4L, 0L, 0L))
self.domain.interfaceStats('vnet1').AndReturn((5L, 6L, 0L, 0L,
7L, 8L, 0L, 0L))
self.mox.ReplayAll()
interfaces = list(self.inspector.inspect_vnics(self.instance_name))
self.assertEquals(len(interfaces), 2)
vnic0, info0 = interfaces[0]
self.assertEqual(vnic0.name, 'vnet0')
self.assertEqual(vnic0.mac, 'fa:16:3e:71:ec:6d')
self.assertEqual(vnic0.fref,
'nova-instance-00000001-fa163e71ec6d')
self.assertEqual(vnic0.parameters.get('projmask'), '255.255.255.0')
self.assertEqual(vnic0.parameters.get('ip'), '10.0.0.2')
self.assertEqual(vnic0.parameters.get('projnet'), '10.0.0.0')
self.assertEqual(vnic0.parameters.get('dhcpserver'), '10.0.0.1')
self.assertEqual(info0.rx_bytes, 1L)
self.assertEqual(info0.rx_packets, 2L)
self.assertEqual(info0.tx_bytes, 3L)
self.assertEqual(info0.tx_packets, 4L)
vnic1, info1 = interfaces[1]
self.assertEqual(vnic1.name, 'vnet1')
self.assertEqual(vnic1.mac, 'fa:16:3e:71:ec:6e')
self.assertEqual(vnic1.fref,
'nova-instance-00000001-fa163e71ec6e')
self.assertEqual(vnic1.parameters.get('projmask'), '255.255.255.0')
self.assertEqual(vnic1.parameters.get('ip'), '192.168.0.2')
self.assertEqual(vnic1.parameters.get('projnet'), '192.168.0.0')
self.assertEqual(vnic1.parameters.get('dhcpserver'), '192.168.0.1')
self.assertEqual(info1.rx_bytes, 5L)
self.assertEqual(info1.rx_packets, 6L)
self.assertEqual(info1.tx_bytes, 7L)
self.assertEqual(info1.tx_packets, 8L)
def test_inspect_disks(self):
dom_xml = """
<domain type='kvm'>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none'/>
<source file='/path/instance-00000001/disk'/>
<target dev='vda' bus='virtio'/>
<alias name='virtio-disk0'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x0'/>
</disk>
</devices>
</domain>
"""
self.domain.XMLDesc(0).AndReturn(dom_xml)
self.domain.blockStats('vda').AndReturn((1L, 2L, 3L, 4L, -1))
self.mox.ReplayAll()
disks = list(self.inspector.inspect_disks(self.instance_name))
self.assertEquals(len(disks), 1)
disk0, info0 = disks[0]
self.assertEqual(disk0.device, 'vda')
self.assertEqual(info0.read_requests, 1L)
self.assertEqual(info0.read_bytes, 2L)
self.assertEqual(info0.write_requests, 3L)
self.assertEqual(info0.write_bytes, 4L)

View File

@ -16,3 +16,4 @@ python-novaclient>=2.6.10
python-keystoneclient>=0.2,<0.3
python-swiftclient
pecan
lxml