diff --git a/ceilometer/compute/libvirt.py b/ceilometer/compute/pollsters.py similarity index 65% rename from ceilometer/compute/libvirt.py rename to ceilometer/compute/pollsters.py index acf14b700..6602940fd 100644 --- a/ceilometer/compute/libvirt.py +++ b/ceilometer/compute/pollsters.py @@ -1,8 +1,10 @@ # -*- encoding: utf-8 -*- # # Copyright © 2012 eNovance +# Copyright © 2012 Red Hat, Inc # # Author: Julien Danjou +# Author: Eoghan Glynn # # 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) diff --git a/ceilometer/compute/virt/__init__.py b/ceilometer/compute/virt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ceilometer/compute/virt/inspector.py b/ceilometer/compute/virt/inspector.py new file mode 100644 index 000000000..44cbc4caf --- /dev/null +++ b/ceilometer/compute/virt/inspector.py @@ -0,0 +1,130 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 Red Hat, Inc +# +# Author: Eoghan Glynn +# +# 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() diff --git a/ceilometer/compute/virt/libvirt/__init__.py b/ceilometer/compute/virt/libvirt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ceilometer/compute/virt/libvirt/inspector.py b/ceilometer/compute/virt/libvirt/inspector.py new file mode 100644 index 000000000..23ffa97de --- /dev/null +++ b/ceilometer/compute/virt/libvirt/inspector.py @@ -0,0 +1,139 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 Red Hat, Inc +# +# Author: Eoghan Glynn +# +# 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) diff --git a/doc/source/contributing/plugins.rst b/doc/source/contributing/plugins.rst index aea4b8af3..1f8a43992 100644 --- a/doc/source/contributing/plugins.rst +++ b/doc/source/contributing/plugins.rst @@ -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, diff --git a/setup.py b/setup.py index 3c17218f7..fa345f824 100755 --- a/setup.py +++ b/setup.py @@ -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 """), ) diff --git a/tests/compute/test_libvirt.py b/tests/compute/test_libvirt.py deleted file mode 100644 index fe5965e22..000000000 --- a/tests/compute/test_libvirt.py +++ /dev/null @@ -1,249 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# -# Copyright © 2012 eNovance -# -# Author: Julien Danjou -# -# 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 = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - """ - - 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)) diff --git a/tests/compute/test_pollsters.py b/tests/compute/test_pollsters.py new file mode 100644 index 000000000..ae0250301 --- /dev/null +++ b/tests/compute/test_pollsters.py @@ -0,0 +1,179 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 eNovance +# Copyright © 2012 Red Hat, Inc +# +# Author: Julien Danjou +# Author: Eoghan Glynn +# +# 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)) diff --git a/tests/compute/virt/__init__.py b/tests/compute/virt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/compute/virt/libvirt/__init__.py b/tests/compute/virt/libvirt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/compute/virt/libvirt/test_inspector.py b/tests/compute/virt/libvirt/test_inspector.py new file mode 100644 index 000000000..2b2c94b68 --- /dev/null +++ b/tests/compute/virt/libvirt/test_inspector.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 Red Hat, Inc +# +# Author: Eoghan Glynn +# +# 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 = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + + 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 = """ + + + + + + + +
+ + + + """ + + 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) diff --git a/tools/pip-requires b/tools/pip-requires index a6f395ffb..4a0755eb5 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -16,3 +16,4 @@ python-novaclient>=2.6.10 python-keystoneclient>=0.2,<0.3 python-swiftclient pecan +lxml