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:
parent
b12376fed9
commit
40a3874c38
@ -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)
|
0
ceilometer/compute/virt/__init__.py
Normal file
0
ceilometer/compute/virt/__init__.py
Normal file
130
ceilometer/compute/virt/inspector.py
Normal file
130
ceilometer/compute/virt/inspector.py
Normal 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()
|
0
ceilometer/compute/virt/libvirt/__init__.py
Normal file
0
ceilometer/compute/virt/libvirt/__init__.py
Normal file
139
ceilometer/compute/virt/libvirt/inspector.py
Normal file
139
ceilometer/compute/virt/libvirt/inspector.py
Normal 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)
|
@ -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,
|
||||
|
11
setup.py
11
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
|
||||
"""),
|
||||
)
|
||||
|
@ -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))
|
179
tests/compute/test_pollsters.py
Normal file
179
tests/compute/test_pollsters.py
Normal 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))
|
0
tests/compute/virt/__init__.py
Normal file
0
tests/compute/virt/__init__.py
Normal file
0
tests/compute/virt/libvirt/__init__.py
Normal file
0
tests/compute/virt/libvirt/__init__.py
Normal file
151
tests/compute/virt/libvirt/test_inspector.py
Normal file
151
tests/compute/virt/libvirt/test_inspector.py
Normal 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)
|
@ -16,3 +16,4 @@ python-novaclient>=2.6.10
|
||||
python-keystoneclient>=0.2,<0.3
|
||||
python-swiftclient
|
||||
pecan
|
||||
lxml
|
||||
|
Loading…
Reference in New Issue
Block a user