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 -*-
|
# -*- 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)
|
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,
|
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,
|
||||||
|
11
setup.py
11
setup.py
@ -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
|
||||||
"""),
|
"""),
|
||||||
)
|
)
|
||||||
|
@ -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-keystoneclient>=0.2,<0.3
|
||||||
python-swiftclient
|
python-swiftclient
|
||||||
pecan
|
pecan
|
||||||
|
lxml
|
||||||
|
Loading…
Reference in New Issue
Block a user