Merge "Report minimal metrics for stopped (SHUTOFF) VMs"

This commit is contained in:
Zuul
2026-06-17 06:44:01 +00:00
committed by Gerrit Code Review
5 changed files with 158 additions and 18 deletions
+6
View File
@@ -63,6 +63,12 @@ OPTS = [
"of resource_update_interval. This option is only used "
"for agent polling to Nova API, so it will work only "
"when 'instance_discovery_method' is set to 'naive'."),
cfg.BoolOpt('report_stopped_instance_metrics',
default=False,
help="Report a minimal set of metrics (power.state, vcpus, "
"memory, disk.device.capacity, disk.device.usage) for "
"stopped (SHUTOFF) instances. Only works with the "
"'libvirt_metadata' instance discovery method."),
cfg.BoolOpt('fetch_extra_metadata',
default=False,
help="Whether or not additional instance attributes that "
+25 -12
View File
@@ -61,23 +61,24 @@ class LibvirtInspector(virt_inspector.Inspector):
except Exception as ex:
raise virt_inspector.InspectorException(str(ex))
def _get_domain_not_shut_off_or_raise(self, instance):
def _get_domain(self, instance, raise_if_shutoff):
instance_name = util.instance_name(instance)
domain = self._lookup_by_uuid(instance)
state = domain.info()[0]
if state == libvirt.VIR_DOMAIN_SHUTOFF:
msg = _('Failed to inspect data of instance '
'<name=%(name)s, id=%(id)s>, '
'domain state is SHUTOFF.') % {
'name': instance_name, 'id': instance.id}
raise virt_inspector.InstanceShutOffException(msg)
if raise_if_shutoff:
state = domain.info()[0]
if state == libvirt.VIR_DOMAIN_SHUTOFF:
msg = _('Failed to inspect data of instance '
'<name=%(name)s, id=%(id)s>, '
'domain state is SHUTOFF.') % {
'name': instance_name, 'id': instance.id}
raise virt_inspector.InstanceShutOffException(msg)
return domain
@libvirt_utils.retry_on_disconnect
def inspect_vnics(self, instance, duration):
domain = self._get_domain_not_shut_off_or_raise(instance)
domain = self._get_domain(instance, True)
tree = etree.fromstring(domain.XMLDesc(0))
for iface in tree.findall('devices/interface'):
@@ -169,7 +170,7 @@ class LibvirtInspector(virt_inspector.Inspector):
@libvirt_utils.retry_on_disconnect
def inspect_disks(self, instance, duration):
domain = self._get_domain_not_shut_off_or_raise(instance)
domain = self._get_domain(instance, True)
for device in self._get_disk_devices(domain):
try:
block_stats = domain.blockStats(device)
@@ -191,7 +192,8 @@ class LibvirtInspector(virt_inspector.Inspector):
@libvirt_utils.retry_on_disconnect
def inspect_disk_info(self, instance, duration):
domain = self._get_domain_not_shut_off_or_raise(instance)
domain = self._get_domain(
instance, not self.conf.compute.report_stopped_instance_metrics)
for device in self._get_disk_devices(domain):
block_info = domain.blockInfo(device)
# if vm mount cdrom, libvirt will align by 4K bytes, capacity may
@@ -206,7 +208,18 @@ class LibvirtInspector(virt_inspector.Inspector):
@libvirt_utils.raise_nodata_if_unsupported
@libvirt_utils.retry_on_disconnect
def inspect_instance(self, instance, duration=None):
domain = self._get_domain_not_shut_off_or_raise(instance)
domain = self._get_domain(
instance, not self.conf.compute.report_stopped_instance_metrics)
if self.conf.compute.report_stopped_instance_metrics:
dom_info = domain.info()
state = dom_info[0]
if state == libvirt.VIR_DOMAIN_SHUTOFF:
return virt_inspector.InstanceStats(
power_state=state,
cpu_number=dom_info[3],
memory_actual=dom_info[1] / units.Ki,
)
memory_actual = None
memory_available = None
@@ -129,3 +129,46 @@ class TestVCPUsPollster(base.TestPollsterBase):
_verify_cpu_metering(1)
_verify_cpu_metering(1)
_verify_cpu_metering(2)
def test_get_samples_shutoff(self):
self._mock_inspect_instance(
virt_inspector.InstanceStats(power_state=5, cpu_number=4,
memory_actual=2048.0),
)
mgr = manager.AgentManager(0, self.CONF)
pollster = instance_stats.VCPUsPollster(self.CONF)
cache = {}
samples = list(pollster.get_samples(mgr, cache, [self.instance]))
self.assertEqual(1, len(samples))
self.assertEqual('vcpus', samples[0].name)
self.assertEqual(4, samples[0].volume)
class TestPowerStatePollster(base.TestPollsterBase):
def test_get_samples_shutoff(self):
self._mock_inspect_instance(
virt_inspector.InstanceStats(power_state=5, cpu_number=2,
memory_actual=1024.0),
)
mgr = manager.AgentManager(0, self.CONF)
pollster = instance_stats.PowerStatePollster(self.CONF)
cache = {}
samples = list(pollster.get_samples(mgr, cache, [self.instance]))
self.assertEqual(1, len(samples))
self.assertEqual('power.state', samples[0].name)
self.assertEqual(5, samples[0].volume)
class TestCPUPollsterShutoff(base.TestPollsterBase):
def test_get_samples_shutoff_no_cpu_time(self):
self._mock_inspect_instance(
virt_inspector.InstanceStats(power_state=5, cpu_number=2,
memory_actual=1024.0),
)
mgr = manager.AgentManager(0, self.CONF)
pollster = instance_stats.CPUPollster(self.CONF)
cache = {}
samples = list(pollster.get_samples(mgr, cache, [self.instance]))
self.assertEqual(0, len(samples))
@@ -111,7 +111,7 @@ class TestLibvirtInspection(base.BaseTestCase):
self.assertEqual(2, stats.cpu_number)
self.assertEqual(999999, stats.cpu_time)
def test_inspect_cpus_with_domain_shutoff(self):
def test_inspect_instance_with_domain_shutoff(self):
domain = mock.Mock()
domain.info.return_value = (5, 0, 0, 2, 999999)
conn = mock.Mock()
@@ -123,6 +123,31 @@ class TestLibvirtInspection(base.BaseTestCase):
self.inspector.inspect_instance,
self.instance, None)
def test_inspect_instance_with_domain_shutoff_reporting_enabled(self):
self.inspector.conf.set_override(
'report_stopped_instance_metrics', True, group='compute')
domain = mock.Mock()
domain.info.return_value = (5, 2097152, 0, 2, 0)
conn = mock.Mock()
conn.lookupByUUIDString.return_value = domain
with mock.patch('ceilometer.compute.virt.libvirt.utils.'
'refresh_libvirt_connection', return_value=conn):
stats = self.inspector.inspect_instance(self.instance, None)
self.assertEqual(5, stats.power_state)
self.assertEqual(2, stats.cpu_number)
self.assertEqual(2097152 / units.Ki, stats.memory_actual)
self.assertIsNone(stats.cpu_time)
self.assertIsNone(stats.memory_available)
self.assertIsNone(stats.memory_usage)
self.assertIsNone(stats.memory_resident)
self.assertIsNone(stats.memory_swap_in)
self.assertIsNone(stats.memory_swap_out)
self.assertIsNone(stats.cpu_cycles)
self.assertIsNone(stats.instructions)
self.assertIsNone(stats.cache_references)
self.assertIsNone(stats.cache_misses)
def test_inspect_vnics(self):
dom_xml = """
<domain type='kvm'>
@@ -352,6 +377,42 @@ class TestLibvirtInspection(base.BaseTestCase):
self.assertEqual(2, disks[0].allocation)
self.assertEqual(3, disks[0].physical)
def test_inspect_disk_info_with_domain_shutoff(self):
self.inspector.conf.set_override(
'report_stopped_instance_metrics', True, group='compute')
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>
"""
domain = mock.Mock()
domain.XMLDesc.return_value = dom_xml
domain.blockInfo.return_value = (10737418240, 2147483648, 3221225472,
-1)
domain.info.return_value = (5, 0, 0, 2, 0)
conn = mock.Mock()
conn.lookupByUUIDString.return_value = domain
with mock.patch('ceilometer.compute.virt.libvirt.utils.'
'refresh_libvirt_connection', return_value=conn):
disks = list(self.inspector.inspect_disk_info(
self.instance, None))
self.assertEqual(1, len(disks))
self.assertEqual('vda', disks[0].device)
self.assertEqual(10737418240, disks[0].capacity)
self.assertEqual(2147483648, disks[0].allocation)
self.assertEqual(3221225472, disks[0].physical)
def test_inspect_disk_info_network_type(self):
dom_xml = """
<domain type='kvm'>
@@ -445,17 +506,23 @@ class TestLibvirtInspection(base.BaseTestCase):
self.assertEqual(0, len(disks))
def test_inspect_memory_usage_with_domain_shutoff(self):
def test_inspect_memory_with_domain_shutoff(self):
self.inspector.conf.set_override(
'report_stopped_instance_metrics', True, group='compute')
domain = mock.Mock()
domain.info.return_value = (5, 0, 51200, 2, 999999)
domain.info.return_value = (5, 524288, 51200, 4, 0)
conn = mock.Mock()
conn.lookupByUUIDString.return_value = domain
with mock.patch('ceilometer.compute.virt.libvirt.utils.'
'refresh_libvirt_connection', return_value=conn):
self.assertRaises(virt_inspector.InstanceShutOffException,
self.inspector.inspect_instance,
self.instance, None)
stats = self.inspector.inspect_instance(self.instance, None)
self.assertEqual(5, stats.power_state)
self.assertEqual(4, stats.cpu_number)
self.assertEqual(524288 / units.Ki, stats.memory_actual)
self.assertIsNone(stats.memory_usage)
self.assertIsNone(stats.memory_available)
self.assertIsNone(stats.memory_resident)
def test_inspect_memory_with_empty_stats(self):
domain = mock.Mock()
@@ -0,0 +1,11 @@
---
features:
- |
A new ``[compute] report_stopped_instance_metrics`` option (disabled by
default) allows the libvirt compute pollster to report a minimal set
of metrics for stopped (SHUTOFF) VMs: ``power.state``, ``vcpus``,
``memory``, ``disk.device.capacity``, and ``disk.device.usage``.
Runtime metrics (such as ``cpu``, ``memory.usage``,
``memory.resident``, network and disk I/O stats) are never reported
for stopped VMs. Shelved-offloaded VMs are not affected as Nova
removes their libvirt domain entirely.