diff --git a/nova/tests/unit/virt/xenapi/test_xenapi.py b/nova/tests/unit/virt/xenapi/test_xenapi.py index fb09b02f92b..e1e976cf84b 100644 --- a/nova/tests/unit/virt/xenapi/test_xenapi.py +++ b/nova/tests/unit/virt/xenapi/test_xenapi.py @@ -420,26 +420,51 @@ def fake_get_rrd(host, vm_uuid): self.assertThat(actual, matchers.DictMatches(expected)) def test_get_instance_diagnostics(self): - def fake_get_rrd(host, vm_uuid): - path = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(path, 'vm_rrd.xml')) as f: - return re.sub(r'\s', '', f.read()) - self.stubs.Set(vm_utils, '_get_rrd', fake_get_rrd) - expected = fake_diagnostics.fake_diagnostics_obj( config_drive=False, state='running', driver='xenapi', - cpu_details=[{}, {}, {}, {}], # 4 CPUs with 'None' values - nic_details=[{}], # 1 NIC with 'None' values - disk_details=[{}], # 1 disk with 'None' values - memory_details={'maximum': 8192}) + cpu_details=[{'id': 0, 'utilisation': 11}, + {'id': 1, 'utilisation': 22}, + {'id': 2, 'utilisation': 33}, + {'id': 3, 'utilisation': 44}], + nic_details=[{'mac_address': 'DE:AD:BE:EF:00:01', + 'rx_rate': 50, + 'tx_rate': 100}], + disk_details=[{'read_bytes': 50, 'write_bytes': 100}], + memory_details={'maximum': 8192, 'used': 3072}) instance = self._create_instance(obj=True) actual = self.conn.get_instance_diagnostics(instance) self.assertDiagnosticsEqual(expected, actual) + def _test_get_instance_diagnostics_failure(self, **kwargs): + instance = self._create_instance(obj=True) + + with mock.patch.object(xenapi_fake.SessionBase, 'VM_query_data_source', + **kwargs): + actual = self.conn.get_instance_diagnostics(instance) + + expected = fake_diagnostics.fake_diagnostics_obj( + config_drive=False, + state='running', + driver='xenapi', + cpu_details=[{'id': 0}, {'id': 1}, {'id': 2}, {'id': 3}], + nic_details=[{'mac_address': 'DE:AD:BE:EF:00:01'}], + disk_details=[{}], + memory_details={'maximum': None, 'used': None}) + + self.assertDiagnosticsEqual(expected, actual) + + def test_get_instance_diagnostics_xenapi_exception(self): + self._test_get_instance_diagnostics_failure( + side_effect=XenAPI.Failure('')) + + def test_get_instance_diagnostics_nan_value(self): + self._test_get_instance_diagnostics_failure( + return_value=float('NaN')) + def test_get_vnc_console(self): instance = self._create_instance(obj=True) session = get_session() diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 66c0eeeef8c..5fbb77a1379 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -236,7 +236,13 @@ def after_VBD_create(vbd_ref, vbd_rec): is created. """ vbd_rec['currently_attached'] = False - vbd_rec['device'] = '' + + # TODO(snikitin): Find a better way for generating of device name. + # Usually 'userdevice' has numeric values like '1', '2', '3', etc. + # Ideally they should be transformed to something like 'xvda', 'xvdb', + # 'xvdx', etc. But 'userdevice' also may be 'autodetect', 'fake' or even + # unset. We should handle it in future. + vbd_rec['device'] = vbd_rec.get('userdevice', '') vbd_rec.setdefault('other_config', {}) vm_ref = vbd_rec['VM'] @@ -836,6 +842,19 @@ def VM_pause(self, session, vm_ref): db_ref = _db_content['VM'][vm_ref] db_ref['power_state'] = 'Paused' + def VM_query_data_source(self, session, vm_ref, field): + vm = {'cpu0': 0.11, + 'cpu1': 0.22, + 'cpu2': 0.33, + 'cpu3': 0.44, + 'memory': 8 * units.Gi, # 8GB in bytes + 'memory_internal_free': 5 * units.Mi, # 5GB in kilobytes + 'vif_0_rx': 50, + 'vif_0_tx': 100, + 'vbd_0_read': 50, + 'vbd_0_write': 100} + return vm.get(field, 0) + def pool_eject(self, session, host_ref): pass diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d3724af8ee1..2db271a41e2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -20,6 +20,7 @@ """ import contextlib +import math import os import time import urllib @@ -1722,6 +1723,23 @@ def get_power_state(session, vm_ref): return XENAPI_POWER_STATE[xapi_state] +def _vm_query_data_source(session, *args): + """We're getting diagnostics stats from the RRDs which are updated every + 5 seconds. It means that diagnostics information may be incomplete during + first 5 seconds of VM life. In such cases method ``query_data_source()`` + may raise a ``XenAPI.Failure`` exception or may return a `NaN` value. + """ + + try: + value = session.VM.query_data_source(*args) + except session.XenAPI.Failure: + return None + + if math.isnan(value): + return None + return value + + def compile_info(session, vm_ref): """Fill record with VM status information.""" power_state = get_power_state(session, vm_ref) @@ -1735,29 +1753,69 @@ def compile_info(session, vm_ref): num_cpu=num_cpu) -def compile_instance_diagnostics(instance, vm_rec): - vm_power_state_int = XENAPI_POWER_STATE[vm_rec['power_state']] - vm_power_state = power_state.STATE_MAP[vm_power_state_int] +def compile_instance_diagnostics(session, instance, vm_ref): + xen_power_state = session.VM.get_power_state(vm_ref) + vm_power_state = power_state.STATE_MAP[XENAPI_POWER_STATE[xen_power_state]] config_drive = configdrive.required_by(instance) diags = diagnostics.Diagnostics(state=vm_power_state, driver='xenapi', config_drive=config_drive) + _add_cpu_usage(session, vm_ref, diags) + _add_nic_usage(session, vm_ref, diags) + _add_disk_usage(session, vm_ref, diags) + _add_memory_usage(session, vm_ref, diags) - for cpu_num in range(0, int(vm_rec['VCPUs_max'])): - diags.add_cpu() + return diags - for vif in vm_rec['VIFs']: - diags.add_nic() - for vbd in vm_rec['VBDs']: - diags.add_disk() +def _add_cpu_usage(session, vm_ref, diag_obj): + cpu_num = int(session.VM.get_VCPUs_max(vm_ref)) + for cpu_num in range(0, cpu_num): + utilisation = _vm_query_data_source(session, vm_ref, "cpu%d" % cpu_num) + if utilisation is not None: + utilisation *= 100 + diag_obj.add_cpu(id=cpu_num, utilisation=utilisation) - max_mem_bytes = int(vm_rec['memory_dynamic_max']) - diags.memory_details = diagnostics.MemoryDiagnostics( - maximum=max_mem_bytes / units.Mi) - return diags +def _add_nic_usage(session, vm_ref, diag_obj): + vif_refs = session.VM.get_VIFs(vm_ref) + for vif_ref in vif_refs: + vif_rec = session.VIF.get_record(vif_ref) + rx_rate = _vm_query_data_source(session, vm_ref, + "vif_%s_rx" % vif_rec['device']) + tx_rate = _vm_query_data_source(session, vm_ref, + "vif_%s_tx" % vif_rec['device']) + diag_obj.add_nic(mac_address=vif_rec['MAC'], + rx_rate=rx_rate, + tx_rate=tx_rate) + + +def _add_disk_usage(session, vm_ref, diag_obj): + vbd_refs = session.VM.get_VBDs(vm_ref) + for vbd_ref in vbd_refs: + vbd_rec = session.VBD.get_record(vbd_ref) + read_bytes = _vm_query_data_source(session, vm_ref, + "vbd_%s_read" % vbd_rec['device']) + write_bytes = _vm_query_data_source(session, vm_ref, + "vbd_%s_write" % vbd_rec['device']) + diag_obj.add_disk(read_bytes=read_bytes, write_bytes=write_bytes) + + +def _add_memory_usage(session, vm_ref, diag_obj): + total_mem = _vm_query_data_source(session, vm_ref, "memory") + free_mem = _vm_query_data_source(session, vm_ref, "memory_internal_free") + used_mem = None + if total_mem is not None: + # total_mem provided from XenServer is in Bytes. Converting it to MB. + total_mem /= units.Mi + + if free_mem is not None: + # free_mem provided from XenServer is in KB. Converting it to MB. + used_mem = total_mem - free_mem / units.Ki + + diag_obj.memory_details = diagnostics.MemoryDiagnostics( + maximum=total_mem, used=used_mem) def compile_diagnostics(vm_rec): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5418309f8d4..e8da8147ca2 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1776,8 +1776,8 @@ def get_diagnostics(self, instance): def get_instance_diagnostics(self, instance): """Return data about VM diagnostics using the common API.""" vm_ref = self._get_vm_opaque_ref(instance) - vm_rec = self._session.VM.get_record(vm_ref) - return vm_utils.compile_instance_diagnostics(instance, vm_rec) + return vm_utils.compile_instance_diagnostics(self._session, instance, + vm_ref) def _get_vif_device_map(self, vm_rec): vif_map = {}