diff --git a/ceilometer/compute/virt/hyperv/__init__.py b/ceilometer/compute/virt/hyperv/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ceilometer/compute/virt/hyperv/inspector.py b/ceilometer/compute/virt/hyperv/inspector.py new file mode 100644 index 000000000..6a29e01cf --- /dev/null +++ b/ceilometer/compute/virt/hyperv/inspector.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Author: Claudiu Belu +# Alessandro Pilotti +# +# 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 Hyper-V""" + +from oslo.config import cfg + +from ceilometer.compute.virt import inspector as virt_inspector +from ceilometer.compute.virt.hyperv import utilsv2 +from ceilometer.openstack.common import log + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +class HyperVInspector(virt_inspector.Inspector): + + def __init__(self): + super(HyperVInspector, self).__init__() + self._utils = utilsv2.UtilsV2() + + def inspect_instances(self): + for element_name, name in self._utils.get_all_vms(): + yield virt_inspector.Instance( + name=element_name, + UUID=name) + + def inspect_cpus(self, instance_name): + (cpu_clock_used, + cpu_count, uptime) = self._utils.get_cpu_metrics(instance_name) + host_cpu_clock, host_cpu_count = self._utils.get_host_cpu_info() + + cpu_percent_used = (cpu_clock_used / + float(host_cpu_clock * cpu_count)) + # Nanoseconds + cpu_time = (long(uptime * cpu_percent_used) * + 1000) + + return virt_inspector.CPUStats(number=cpu_count, time=cpu_time) + + def inspect_vnics(self, instance_name): + for vnic_metrics in self._utils.get_vnic_metrics(instance_name): + interface = virt_inspector.Interface( + name=vnic_metrics["element_name"], + mac=vnic_metrics["address"], + fref=None, + parameters=None) + + stats = virt_inspector.InterfaceStats( + rx_bytes=vnic_metrics['rx_bytes'], + rx_packets=0, + tx_bytes=vnic_metrics['tx_bytes'], + tx_packets=0) + + yield (interface, stats) + + def inspect_disks(self, instance_name): + for disk_metrics in self._utils.get_disk_metrics(instance_name): + device = dict([(i, disk_metrics[i]) + for i in ['instance_id', 'host_resource'] + if i in disk_metrics]) + + disk = virt_inspector.Disk(device=device) + stats = virt_inspector.DiskStats( + read_requests=0, + # Return bytes + read_bytes=disk_metrics['read_mb'] * 1024, + write_requests=0, + write_bytes=disk_metrics['write_mb'] * 1024, + errors=0) + + yield (disk, stats) diff --git a/ceilometer/compute/virt/hyperv/utilsv2.py b/ceilometer/compute/virt/hyperv/utilsv2.py new file mode 100644 index 000000000..d0e85215c --- /dev/null +++ b/ceilometer/compute/virt/hyperv/utilsv2.py @@ -0,0 +1,193 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Author: Claudiu Belu +# Alessandro Pilotti +# +# 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. +""" +Utility class for VM related operations. +Based on the "root/virtualization/v2" namespace available starting with +Hyper-V Server / Windows Server 2012. +""" + +import sys + +if sys.platform == 'win32': + import wmi + +from oslo.config import cfg + +from ceilometer.compute.virt import inspector +from ceilometer.openstack.common.gettextutils import _ +from ceilometer.openstack.common import log as logging + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +class HyperVException(inspector.InspectorException): + pass + + +class UtilsV2(object): + + _VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized' + + _PROC_SETTING = 'Msvm_ProcessorSettingData' + _SYNTH_ETH_PORT = 'Msvm_SyntheticEthernetPortSettingData' + _ETH_PORT_ALLOC = 'Msvm_EthernetPortAllocationSettingData' + _STORAGE_ALLOC = 'Msvm_StorageAllocationSettingData' + _VS_SETTING_DATA = 'Msvm_VirtualSystemSettingData' + _AGGREG_METRIC = 'Msvm_AggregationMetricDefinition' + _METRICS_ME = 'Msvm_MetricForME' + + _CPU_METRIC_NAME = 'Aggregated Average CPU Utilization' + _NET_IN_METRIC_NAME = 'Aggregated Filtered Incoming Network Traffic' + _NET_OUT_METRIC_NAME = 'Aggregated Filtered Outgoing Network Traffic' + # Disk metrics are supported from Hyper-V 2012 R2 + _DISK_RD_METRIC_NAME = 'Aggregated Disk Data Read' + _DISK_WR_METRIC_NAME = 'Aggregated Disk Data Written' + + def __init__(self, host='.'): + if sys.platform == 'win32': + self._init_hyperv_wmi_conn(host) + self._init_cimv2_wmi_conn(host) + self._host_cpu_info = None + + def _init_hyperv_wmi_conn(self, host): + self._conn = wmi.WMI(moniker='//%s/root/virtualization/v2' % host) + + def _init_cimv2_wmi_conn(self, host): + self._conn_cimv2 = wmi.WMI(moniker='//%s/root/cimv2' % host) + + def get_host_cpu_info(self): + if not self._host_cpu_info: + host_cpus = self._conn_cimv2.Win32_Processor() + self._host_cpu_info = (host_cpus[0].MaxClockSpeed, len(host_cpus)) + return self._host_cpu_info + + def get_all_vms(self): + vms = [(v.ElementName, v.Name) for v in + self._conn.Msvm_ComputerSystem(['ElementName', 'Name'], + Caption="Virtual Machine")] + return vms + + def get_cpu_metrics(self, vm_name): + vm = self._lookup_vm(vm_name) + cpu_sd = self._get_vm_resources(vm, self._PROC_SETTING)[0] + cpu_metrics_def = self._get_metric_def(self._CPU_METRIC_NAME) + cpu_metric_aggr = self._get_metrics(vm, cpu_metrics_def)[0] + + return (int(cpu_metric_aggr.MetricValue), + cpu_sd.VirtualQuantity, + long(vm.OnTimeInMilliseconds)) + + def get_vnic_metrics(self, vm_name): + vm = self._lookup_vm(vm_name) + ports = self._get_vm_resources(vm, self._ETH_PORT_ALLOC) + vnics = self._get_vm_resources(vm, self._SYNTH_ETH_PORT) + + metric_def_in = self._get_metric_def(self._NET_IN_METRIC_NAME) + metric_def_out = self._get_metric_def(self._NET_OUT_METRIC_NAME) + + for port in ports: + vnic = [v for v in vnics if port.Parent == v.path_()][0] + metric_values = self._get_metric_values( + port, [metric_def_in, metric_def_out]) + + yield { + 'rx_bytes': metric_values[0], + 'tx_bytes': metric_values[1], + 'element_name': vnic.ElementName, + 'address': vnic.Address + } + + def get_disk_metrics(self, vm_name): + vm = self._lookup_vm(vm_name) + metric_def_r = self._get_metric_def(self._DISK_RD_METRIC_NAME) + metric_def_w = self._get_metric_def(self._DISK_WR_METRIC_NAME) + + disks = self._get_vm_resources(vm, self._STORAGE_ALLOC) + for disk in disks: + metric_values = self._get_metric_values( + disk, [metric_def_r, metric_def_w]) + + # Thi sis e.g. the VHD file location + if disk.HostResource: + host_resource = disk.HostResource[0] + + yield { + # Values are in megabytes + 'read_mb': metric_values[0], + 'write_mb': metric_values[1], + 'instance_id': disk.InstanceID, + 'host_resource': host_resource + } + + def _sum_metric_values(self, metrics): + tot_metric_val = 0 + for metric in metrics: + tot_metric_val += long(metric.MetricValue) + return tot_metric_val + + def _get_metric_values(self, element, metric_defs): + element_metrics = element.associators( + wmi_association_class=self._METRICS_ME) + + metric_values = [] + for metric_def in metric_defs: + if metric_def: + metrics = self._filter_metrics(element_metrics, metric_def) + metric_values.append(self._sum_metric_values(metrics)) + else: + # In case the metric is not defined on this host + metric_values.append(0) + return metric_values + + def _lookup_vm(self, vm_name): + vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) + n = len(vms) + if n == 0: + raise inspector.InstanceNotFoundException( + _('VM %s not found on Hyper-V') % vm_name) + elif n > 1: + raise HyperVException(_('Duplicate VM name found: %s') % vm_name) + else: + return vms[0] + + def _get_metrics(self, element, metric_def): + return self._filter_metrics( + element.associators( + wmi_association_class=self._METRICS_ME), metric_def) + + def _filter_metrics(self, all_metrics, metric_def): + return [v for v in all_metrics if + v.MetricDefinitionId == metric_def.Id] + + def _get_metric_def(self, metric_def): + metric = self._conn.CIM_BaseMetricDefinition(ElementName=metric_def) + if metric: + return metric[0] + + def _get_vm_setting_data(self, vm): + vm_settings = vm.associators( + wmi_result_class=self._VS_SETTING_DATA) + # Avoid snapshots + return [s for s in vm_settings if + s.VirtualSystemType == self._VIRTUAL_SYSTEM_TYPE_REALIZED][0] + + def _get_vm_resources(self, vm, resource_class): + setting_data = self._get_vm_setting_data(vm) + return setting_data.associators(wmi_result_class=resource_class) diff --git a/setup.cfg b/setup.cfg index e9fdc0ca5..dbc85f9ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -83,6 +83,7 @@ ceilometer.storage = ceilometer.compute.virt = libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector + hyperv = ceilometer.compute.virt.hyperv.inspector:HyperVInspector ceilometer.transformer = accumulator = ceilometer.transformer.accumulator:TransformerAccumulator diff --git a/tests/compute/virt/hyperv/__init__.py b/tests/compute/virt/hyperv/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/compute/virt/hyperv/test_inspector.py b/tests/compute/virt/hyperv/test_inspector.py new file mode 100644 index 000000000..05477f40c --- /dev/null +++ b/tests/compute/virt/hyperv/test_inspector.py @@ -0,0 +1,126 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Author: Alessandro Pilotti +# +# 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 Hyper-V inspector. +""" + +import mock + +from ceilometer.compute.virt.hyperv import inspector as hyperv_inspector +from ceilometer.tests import base as test_base + + +class TestHyperVInspection(test_base.TestCase): + + def setUp(self): + self._inspector = hyperv_inspector.HyperVInspector() + self._inspector._utils = mock.MagicMock() + + super(TestHyperVInspection, self).setUp() + + def test_inspect_instances(self): + fake_name = 'fake_name' + fake_uuid = 'fake_uuid' + fake_instances = [(fake_name, fake_uuid)] + self._inspector._utils.get_all_vms.return_value = fake_instances + + inspected_instances = list(self._inspector.inspect_instances()) + + self.assertEqual(1, len(inspected_instances)) + self.assertEqual(fake_name, inspected_instances[0].name) + self.assertEqual(fake_uuid, inspected_instances[0].UUID) + + def test_inspect_cpus(self): + fake_instance_name = 'fake_instance_name' + fake_host_cpu_clock = 1000 + fake_host_cpu_count = 2 + fake_cpu_clock_used = 2000 + fake_cpu_count = 3000 + fake_uptime = 4000 + + fake_cpu_percent_used = (fake_cpu_clock_used / + float(fake_host_cpu_clock * fake_cpu_count)) + fake_cpu_time = (long(fake_uptime * fake_cpu_percent_used) * + 1000) + + self._inspector._utils.get_host_cpu_info.return_value = ( + fake_host_cpu_clock, fake_host_cpu_count) + + self._inspector._utils.get_cpu_metrics.return_value = ( + fake_cpu_clock_used, fake_cpu_count, fake_uptime) + + cpu_stats = self._inspector.inspect_cpus(fake_instance_name) + + self.assertEqual(fake_cpu_count, cpu_stats.number) + self.assertEqual(fake_cpu_time, cpu_stats.time) + + def test_inspect_vnics(self): + fake_instance_name = 'fake_instance_name' + fake_rx_bytes = 1000 + fake_tx_bytes = 2000 + fake_element_name = 'fake_element_name' + fake_address = 'fake_address' + + self._inspector._utils.get_vnic_metrics.return_value = [{ + 'rx_bytes': fake_rx_bytes, + 'tx_bytes': fake_tx_bytes, + 'element_name': fake_element_name, + 'address': fake_address}] + + inspected_vnics = list(self._inspector.inspect_vnics( + fake_instance_name)) + + self.assertEqual(1, len(inspected_vnics)) + self.assertEqual(2, len(inspected_vnics[0])) + + inspected_vnic, inspected_stats = inspected_vnics[0] + + self.assertEqual(fake_element_name, inspected_vnic.name) + self.assertEqual(fake_address, inspected_vnic.mac) + + self.assertEqual(fake_rx_bytes, inspected_stats.rx_bytes) + self.assertEqual(fake_tx_bytes, inspected_stats.tx_bytes) + + def test_inspect_disks(self): + fake_instance_name = 'fake_instance_name' + fake_read_mb = 1000 + fake_write_mb = 2000 + fake_instance_id = "fake_fake_instance_id" + fake_host_resource = "fake_host_resource" + + fake_device = {"instance_id": fake_instance_id, + "host_resource": fake_host_resource} + + self._inspector._utils.get_disk_metrics.return_value = [{ + 'read_mb': fake_read_mb, + 'write_mb': fake_write_mb, + 'instance_id': fake_instance_id, + 'host_resource': fake_host_resource}] + + inspected_disks = list(self._inspector.inspect_disks( + fake_instance_name)) + + self.assertEqual(1, len(inspected_disks)) + self.assertEqual(2, len(inspected_disks[0])) + + inspected_disk, inspected_stats = inspected_disks[0] + + self.assertEqual(fake_device, inspected_disk.device) + + self.assertEqual(fake_read_mb * 1024, inspected_stats.read_bytes) + self.assertEqual(fake_write_mb * 1024, inspected_stats.write_bytes) diff --git a/tests/compute/virt/hyperv/test_utilsv2.py b/tests/compute/virt/hyperv/test_utilsv2.py new file mode 100644 index 000000000..d9f7d1f43 --- /dev/null +++ b/tests/compute/virt/hyperv/test_utilsv2.py @@ -0,0 +1,206 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# +# Author: Alessandro Pilotti +# +# 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 Hyper-V utilsv2. +""" + +import mock + +from ceilometer.compute.virt import inspector +from ceilometer.compute.virt.hyperv import utilsv2 as utilsv2 +from ceilometer.tests import base as test_base + + +class TestUtilsV2(test_base.TestCase): + + def setUp(self): + self._utils = utilsv2.UtilsV2() + self._utils._conn = mock.MagicMock() + self._utils._conn_cimv2 = mock.MagicMock() + + super(TestUtilsV2, self).setUp() + + def test_get_host_cpu_info(self): + _fake_clock_speed = 1000 + _fake_cpu_count = 2 + + mock_cpu = mock.MagicMock() + mock_cpu.MaxClockSpeed = _fake_clock_speed + + self._utils._conn_cimv2.Win32_Processor.return_value = [mock_cpu, + mock_cpu] + cpu_info = self._utils.get_host_cpu_info() + + self.assertEqual(_fake_clock_speed, cpu_info[0]) + self.assertEqual(_fake_cpu_count, cpu_info[1]) + + def test_get_all_vms(self): + fake_vm_element_name = "fake_vm_element_name" + fake_vm_name = "fake_vm_name" + + mock_vm = mock.MagicMock() + mock_vm.ElementName = fake_vm_element_name + mock_vm.Name = fake_vm_name + self._utils._conn.Msvm_ComputerSystem.return_value = [mock_vm] + + vms = self._utils.get_all_vms() + + self.assertEqual((fake_vm_element_name, fake_vm_name), vms[0]) + + def test_get_cpu_metrics(self): + fake_vm_element_name = "fake_vm_element_name" + fake_cpu_count = 2 + fake_uptime = 1000 + fake_cpu_metric_val = 2000 + + self._utils._lookup_vm = mock.MagicMock() + self._utils._lookup_vm().OnTimeInMilliseconds = fake_uptime + + self._utils._get_vm_resources = mock.MagicMock() + mock_res = self._utils._get_vm_resources()[0] + mock_res.VirtualQuantity = fake_cpu_count + + self._utils._get_metrics = mock.MagicMock() + self._utils._get_metrics()[0].MetricValue = fake_cpu_metric_val + + cpu_metrics = self._utils.get_cpu_metrics(fake_vm_element_name) + + self.assertEqual(3, len(cpu_metrics)) + self.assertEqual(fake_cpu_metric_val, cpu_metrics[0]) + self.assertEqual(fake_cpu_count, cpu_metrics[1]) + self.assertEqual(fake_uptime, cpu_metrics[2]) + + def test_get_vnic_metrics(self): + fake_vm_element_name = "fake_vm_element_name" + fake_vnic_element_name = "fake_vnic_name" + fake_vnic_address = "fake_vnic_address" + fake_vnic_path = "fake_vnic_path" + fake_rx_bytes = 1000 + fake_tx_bytes = 2000 + + self._utils._lookup_vm = mock.MagicMock() + self._utils._get_vm_resources = mock.MagicMock() + + mock_port = mock.MagicMock() + mock_port.Parent = fake_vnic_path + + mock_vnic = mock.MagicMock() + mock_vnic.path_.return_value = fake_vnic_path + mock_vnic.ElementName = fake_vnic_element_name + mock_vnic.Address = fake_vnic_address + + self._utils._get_vm_resources.side_effect = [[mock_port], [mock_vnic]] + + self._utils._get_metric_def = mock.MagicMock() + + self._utils._get_metric_values = mock.MagicMock() + self._utils._get_metric_values.return_value = [fake_rx_bytes, + fake_tx_bytes] + + vnic_metrics = list(self._utils.get_vnic_metrics(fake_vm_element_name)) + + self.assertEqual(1, len(vnic_metrics)) + self.assertEqual(fake_rx_bytes, vnic_metrics[0]['rx_bytes']) + self.assertEqual(fake_tx_bytes, vnic_metrics[0]['tx_bytes']) + self.assertEqual(fake_vnic_element_name, + vnic_metrics[0]['element_name']) + self.assertEqual(fake_vnic_address, vnic_metrics[0]['address']) + + def test_get_disk_metrics(self): + fake_vm_element_name = "fake_vm_element_name" + fake_host_resource = "fake_host_resource" + fake_instance_id = "fake_instance_id" + fake_read_mb = 1000 + fake_write_mb = 2000 + + self._utils._lookup_vm = mock.MagicMock() + + mock_disk = mock.MagicMock() + mock_disk.HostResource = [fake_host_resource] + mock_disk.InstanceID = fake_instance_id + self._utils._get_vm_resources = mock.MagicMock( + return_value=[mock_disk]) + + self._utils._get_metric_def = mock.MagicMock() + + self._utils._get_metric_values = mock.MagicMock() + self._utils._get_metric_values.return_value = [fake_read_mb, + fake_write_mb] + + disk_metrics = list(self._utils.get_disk_metrics(fake_vm_element_name)) + + self.assertEqual(1, len(disk_metrics)) + self.assertEqual(fake_read_mb, disk_metrics[0]['read_mb']) + self.assertEqual(fake_write_mb, disk_metrics[0]['write_mb']) + self.assertEqual(fake_instance_id, disk_metrics[0]['instance_id']) + self.assertEqual(fake_host_resource, disk_metrics[0]['host_resource']) + + def test_lookup_vm(self): + fake_vm_element_name = "fake_vm_element_name" + fake_vm = "fake_vm" + self._utils._conn.Msvm_ComputerSystem.return_value = [fake_vm] + + vm = self._utils._lookup_vm(fake_vm_element_name) + + self.assertEqual(fake_vm, vm) + + def test_lookup_vm_not_found(self): + fake_vm_element_name = "fake_vm_element_name" + self._utils._conn.Msvm_ComputerSystem.return_value = [] + + self.assertRaises(inspector.InstanceNotFoundException, + self._utils._lookup_vm, fake_vm_element_name) + + def test_lookup_vm_duplicate_found(self): + fake_vm_element_name = "fake_vm_element_name" + fake_vm = "fake_vm" + self._utils._conn.Msvm_ComputerSystem.return_value = [fake_vm, fake_vm] + + self.assertRaises(utilsv2.HyperVException, + self._utils._lookup_vm, fake_vm_element_name) + + def test_get_metric_values(self): + fake_metric_def_id = "fake_metric_def_id" + fake_metric_value = "1000" + + mock_metric = mock.MagicMock() + mock_metric.MetricDefinitionId = fake_metric_def_id + mock_metric.MetricValue = fake_metric_value + + mock_element = mock.MagicMock() + mock_element.associators.return_value = [mock_metric] + + mock_metric_def = mock.MagicMock() + mock_metric_def.Id = fake_metric_def_id + + metric_values = self._utils._get_metric_values(mock_element, + [mock_metric_def]) + + self.assertEqual(1, len(metric_values)) + self.assertEqual(long(fake_metric_value), metric_values[0]) + + def test_get_vm_setting_data(self): + mock_vm_s = mock.MagicMock() + mock_vm_s.VirtualSystemType = self._utils._VIRTUAL_SYSTEM_TYPE_REALIZED + + mock_vm = mock.MagicMock() + mock_vm.associators.return_value = [mock_vm_s] + + vm_setting_data = self._utils._get_vm_setting_data(mock_vm) + + self.assertEqual(mock_vm_s, vm_setting_data)