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 -*- # -*- encoding: utf-8 -*-
# #
# Copyright © 2012 eNovance <licensing@enovance.com> # Copyright © 2012 eNovance <licensing@enovance.com>
# Copyright © 2012 Red Hat, Inc
# #
# Author: Julien Danjou <julien@danjou.info> # Author: Julien Danjou <julien@danjou.info>
# Author: Eoghan Glynn <eglynn@redhat.com>
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
@ -19,58 +21,44 @@
import copy import copy
import datetime 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 import counter
from ceilometer.compute import plugin from ceilometer.compute import plugin
from ceilometer.compute import instance as compute_instance 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 importutils
from ceilometer.openstack.common import log from ceilometer.openstack.common import log
from ceilometer.openstack.common import timeutils 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): def _instance_name(instance):
"""Shortcut to get instance name""" """Shortcut to get instance name"""
return getattr(instance, 'OS-EXT-SRV-ATTR:instance_name', None) return getattr(instance, 'OS-EXT-SRV-ATTR:instance_name', None)
def get_compute_driver(): def get_hypervisor_inspector():
# 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.
try: try:
try: namespace = 'ceilometer.compute.virt'
return driver.load_compute_driver(None) mgr = driver.DriverManager(namespace,
except AttributeError: CONF.hypervisor_inspector,
return importutils.import_object_ns('nova.virt', invoke_on_load=True)
get_compute_driver()) return mgr.driver
except ImportError: except ImportError as e:
# Fall back to the way it was done in Essex. LOG.error("Unable to load the hypervisor inspector: %s" % (e))
import nova.virt.connection return virt_inspector.Inspector()
return nova.virt.connection.get_connection(read_only=True)
def make_counter_from_instance(instance, name, type, volume): 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): inspector = None
# XXX(llu): Keeps Folsom compatibility
try: def __init__(self):
return driver.compute_driver_matches("LibvirtDriver") if not HypervisorPollster.inspector:
except AttributeError: HypervisorPollster.inspector = get_hypervisor_inspector()
# Use a fairly liberal substring check.
return 'libvirt' in get_compute_driver().lower()
class InstancePollster(LibVirtPollster): class InstancePollster(HypervisorPollster):
def get_counters(self, manager, instance): def get_counters(self, manager, instance):
yield make_counter_from_instance(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') LOG = log.getLogger(__name__ + '.diskio')
@ -127,28 +113,21 @@ class DiskIOPollster(LibVirtPollster):
]) ])
def get_counters(self, manager, instance): def get_counters(self, manager, instance):
conn = get_libvirt_connection()
instance_name = _instance_name(instance) instance_name = _instance_name(instance)
try: 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_bytes = 0
r_requests = 0 r_requests = 0
w_bytes = 0 w_bytes = 0
w_requests = 0 w_requests = 0
for disk in disks: for disk, info in self.inspector.inspect_disks(instance_name):
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.device, info.read_requests,
stats[2], stats[3], stats[4]) info.read_bytes, info.write_requests,
r_bytes += stats[0] info.write_bytes, info.errors)
r_requests += stats[1] r_bytes += info.read_bytes
w_bytes += stats[3] r_requests += info.read_requests
w_requests += stats[2] w_bytes += info.write_bytes
w_requests += info.write_requests
yield make_counter_from_instance(instance, yield make_counter_from_instance(instance,
name='disk.read.requests', name='disk.read.requests',
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
@ -169,9 +148,13 @@ class DiskIOPollster(LibVirtPollster):
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
volume=w_bytes, 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') LOG = log.getLogger(__name__ + '.cpu')
@ -179,7 +162,7 @@ class CPUPollster(LibVirtPollster):
def get_cpu_util(self, instance, cpu_info): def get_cpu_util(self, instance, cpu_info):
prev_times = self.utilization_map.get(instance.id) 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()) datetime.datetime.now())
cpu_util = 0.0 cpu_util = 0.0
if prev_times: if prev_times:
@ -187,21 +170,20 @@ class CPUPollster(LibVirtPollster):
prev_timestamp = prev_times[1] prev_timestamp = prev_times[1]
delta = self.utilization_map[instance.id][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 = 1.0 / cpu_info['num_cpu'] cores_fraction = 1.0 / cpu_info.number
# account for cpu_time being reset when the instance is restarted # account for cpu_time being reset when the instance is restarted
time_used = (cpu_info['cpu_time'] - prev_cpu time_used = (cpu_info.time - prev_cpu
if prev_cpu <= cpu_info['cpu_time'] else if prev_cpu <= cpu_info.time else cpu_info.time)
cpu_info['cpu_time'])
cpu_util = 100 * cores_fraction * time_used / elapsed cpu_util = 100 * cores_fraction * time_used / elapsed
return cpu_util return cpu_util
def get_counters(self, manager, instance): def get_counters(self, manager, instance):
conn = get_libvirt_connection()
self.LOG.info('checking instance %s', instance.id) self.LOG.info('checking instance %s', instance.id)
instance_name = _instance_name(instance)
try: 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", 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) cpu_util = self.get_cpu_util(instance, cpu_info)
self.LOG.info("CPU UTILIZATION %%: %s %0.2f", self.LOG.info("CPU UTILIZATION %%: %s %0.2f",
instance.__dict__, cpu_util) instance.__dict__, cpu_util)
@ -218,7 +200,7 @@ class CPUPollster(LibVirtPollster):
yield make_counter_from_instance(instance, yield make_counter_from_instance(instance,
name='cpu', name='cpu',
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
volume=cpu_info['cpu_time'], volume=cpu_info.time,
) )
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',
@ -226,31 +208,17 @@ class CPUPollster(LibVirtPollster):
self.LOG.exception(err) self.LOG.exception(err)
class NetPollster(LibVirtPollster): class NetPollster(HypervisorPollster):
LOG = log.getLogger(__name__ + '.net') LOG = log.getLogger(__name__ + '.net')
NET_USAGE_MESSAGE = ' '.join(["NETWORK USAGE:", "%s %s:", "read-bytes=%d", NET_USAGE_MESSAGE = ' '.join(["NETWORK USAGE:", "%s %s:", "read-bytes=%d",
"write-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 @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) metadata = copy.copy(vnic_data)
resource_metadata = dict(zip(metadata._fields, metadata))
resource_metadata['instance_id'] = instance.id resource_metadata['instance_id'] = instance.id
return counter.Counter( return counter.Counter(
@ -259,50 +227,43 @@ class NetPollster(LibVirtPollster):
volume=volume, volume=volume,
user_id=instance.user_id, user_id=instance.user_id,
project_id=instance.tenant_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
) )
def get_counters(self, manager, instance): def get_counters(self, manager, instance):
conn = get_libvirt_connection()
instance_name = _instance_name(instance) instance_name = _instance_name(instance)
self.LOG.info('checking instance %s', instance.id) self.LOG.info('checking instance %s', instance.id)
try: try:
vnics = self._get_vnics(conn, instance) for vnic, info in self.inspector.inspect_vnics(instance_name):
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'])
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, info.rx_bytes, info.tx_bytes)
yield self.make_vnic_counter(instance, yield self.make_vnic_counter(instance,
name='network.incoming.bytes', name='network.incoming.bytes',
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
volume=rx_bytes, volume=info.rx_bytes,
vnic_data=vnic, vnic_data=vnic,
) )
yield self.make_vnic_counter(instance, yield self.make_vnic_counter(instance,
name='network.outgoing.bytes', name='network.outgoing.bytes',
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
volume=tx_bytes, volume=info.tx_bytes,
vnic_data=vnic, vnic_data=vnic,
) )
yield self.make_vnic_counter(instance, yield self.make_vnic_counter(instance,
name='network.incoming.packets', name='network.incoming.packets',
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
volume=rx_packets, volume=info.rx_packets,
vnic_data=vnic, vnic_data=vnic,
) )
yield self.make_vnic_counter(instance, yield self.make_vnic_counter(instance,
name='network.outgoing.packets', name='network.outgoing.packets',
type=counter.TYPE_CUMULATIVE, type=counter.TYPE_CUMULATIVE,
volume=tx_packets, volume=info.tx_packets,
vnic_data=vnic, 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, will automatically activate all plugins of a given class. For example,
the compute agent will load all plugins of class the compute agent will load all plugins of class
``ceilometer.poll.compute``. This will load, among others, the ``ceilometer.poll.compute``. This will load, among others, the
:class:`ceilometer.compute.libvirt.CPUPollster`, which is defined in :class:`ceilometer.compute.pollsters.CPUPollster`, which is defined in
the file ``ceilometer/compute/libvirt.py`` as well as the the file ``ceilometer/compute/pollsters.py`` as well as the
:class:`ceilometer.compute.notifications.InstanceNotifications` plugin :class:`ceilometer.compute.notifications.InstanceNotifications` plugin
which is defined in the file ``ceilometer/compute/notifications.py`` 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 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 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 "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 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, 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 floatingip = ceilometer.network.notifications:FloatingIP
[ceilometer.poll.compute] [ceilometer.poll.compute]
libvirt_diskio = ceilometer.compute.libvirt:DiskIOPollster diskio = ceilometer.compute.pollsters:DiskIOPollster
libvirt_cpu = ceilometer.compute.libvirt:CPUPollster cpu = ceilometer.compute.pollsters:CPUPollster
libvirt_net = ceilometer.compute.libvirt:NetPollster net = ceilometer.compute.pollsters:NetPollster
libvirt_instance = ceilometer.compute.libvirt:InstancePollster instance = ceilometer.compute.pollsters:InstancePollster
[ceilometer.poll.central] [ceilometer.poll.central]
network_floatingip = ceilometer.network.floatingip:FloatingIPPollster network_floatingip = ceilometer.network.floatingip:FloatingIPPollster
@ -129,5 +129,8 @@ setuptools.setup(
postgresql = ceilometer.storage.impl_sqlalchemy:SQLAlchemyStorage postgresql = ceilometer.storage.impl_sqlalchemy:SQLAlchemyStorage
sqlite = ceilometer.storage.impl_sqlalchemy:SQLAlchemyStorage sqlite = ceilometer.storage.impl_sqlalchemy:SQLAlchemyStorage
test = ceilometer.storage.impl_test:TestDBStorage 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-keystoneclient>=0.2,<0.3
python-swiftclient python-swiftclient
pecan pecan
lxml