Completed implementation of instance diagnostics for Xen

Added data about NICs, CPUs and disks.

blueprint: restore-vm-diagnostics

Change-Id: I3ab0c843f626951ce422ff530b5bc2d266a3d9a2
This commit is contained in:
Sergey Nikitin 2016-11-18 17:07:34 +03:00
parent 0072f70d9b
commit dcf962069c
4 changed files with 131 additions and 29 deletions

View File

@ -420,26 +420,51 @@ class XenAPIVMTestCase(stubs.XenAPITestBase,
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()

View File

@ -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 @@ class SessionBase(object):
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

View File

@ -20,6 +20,7 @@ their attributes like VDIs, VIFs, as well as their lookup functions.
"""
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,31 +1753,71 @@ 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)
for cpu_num in range(0, int(vm_rec['VCPUs_max'])):
diags.add_cpu()
for vif in vm_rec['VIFs']:
diags.add_nic()
for vbd in vm_rec['VBDs']:
diags.add_disk()
max_mem_bytes = int(vm_rec['memory_dynamic_max'])
diags.memory_details = diagnostics.MemoryDiagnostics(
maximum=max_mem_bytes / units.Mi)
_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)
return diags
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)
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):
"""Compile VM diagnostics data."""
try:

View File

@ -1776,8 +1776,8 @@ class VMOps(object):
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 = {}