Fix for bug 2140537

If a guest has pinned CPUs the domain XML's
<iothreadpin> should have iothread attribute also.

Closes-Bug: #2140537
Change-Id: I5c2df747a3fdfbd2ee31d50a3d716a0ccc787e15
Signed-off-by: lajoskatona <lajos.katona@est.tech>
This commit is contained in:
lajoskatona
2026-02-05 19:26:54 +01:00
committed by Lajos Katona
parent 76d796193c
commit 873aee5e95
6 changed files with 175 additions and 159 deletions
+8 -21
View File
@@ -1233,6 +1233,14 @@ class Domain(object):
definition['iothreads'] = iothreads.text
iothread_pin = tree.find('./cputune/iothreadpin')
if iothread_pin is not None and iothread_pin.get('iothread') is None:
raise make_libvirtError(
libvirtError,
"XML error: Missing required attribute 'iothread' "
"in element 'iothreadpin'",
error_code=VIR_ERR_XML_ERROR,
error_domain=VIR_FROM_DOMAIN
)
if iothread_pin is not None:
definition['iothread_pin'] = iothread_pin.get('cpuset')
@@ -2095,27 +2103,6 @@ class Connection(object):
self._emit_lifecycle(dom, VIR_DOMAIN_EVENT_DEFINED, 0)
return dom
# TODO(lajoskatona): Move this validation to defineXML once fix for
# bug/2140537 is merged.
# This method is only used temporarily from
# nova/tests/functional/regressions/test_bug_2140537.py
def _defineXMLIOThreads(self, xml):
xml_doc = etree.fromstring(xml.encode('utf-8'))
iothreadpin = xml_doc.find('./cputune/iothreadpin')
if iothreadpin is not None and iothreadpin.get('iothread') is None:
raise make_libvirtError(
libvirtError,
"XML error: Missing required attribute 'iothread' "
"in element 'iothreadpin'",
error_code=VIR_ERR_XML_ERROR,
error_domain=VIR_FROM_DOMAIN)
dom = Domain(connection=self, running=False, transient=False, xml=xml)
self._vms[dom.name()] = dom
self._emit_lifecycle(dom, VIR_DOMAIN_EVENT_DEFINED, 0)
return dom
def createXML(self, xml, flags):
dom = Domain(connection=self, running=True, transient=True, xml=xml)
self._vms[dom.name()] = dom
@@ -22,7 +22,6 @@ in element 'iothreadpin'".
"""
from lxml import etree
from unittest import mock
from nova.tests.fixtures import libvirt as fakelibvirt
from nova.tests.functional import integrated_helpers
@@ -46,15 +45,6 @@ class TestIOThreadPinningPinnedCPU(
ADDITIONAL_FILTERS = ['NUMATopologyFilter']
def setUp(self):
# TODO(lajoskatona): remove this patch when the fix for
# bug/2140537 is merged, and the libvirt fixture has the
# necessary validation for XML fields for IOThreads.
patcher = mock.patch.object(
fakelibvirt.Connection, 'defineXML',
fakelibvirt.Connection._defineXMLIOThreads)
patcher.start()
self.addCleanup(patcher.stop)
super().setUp()
self.hostname = self.start_compute(
hostname='host1',
@@ -91,20 +81,34 @@ class TestIOThreadPinningPinnedCPU(
# BUG: This fails with libvirt error about missing 'iothread' attribute
# The server creation will fail and go to ERROR state
# server = self._create_server(
# flavor_id=flavor, host='host1', networks='none',
# expected_state='ERROR')
server = self._create_server(
flavor_id=flavor, host='host1', networks='none',
expected_state='ERROR')
expected_state='ACTIVE')
# Verify the server is in ERROR state due to the libvirt XML error
self.assertEqual('ERROR', server['status'])
# self.assertEqual('ERROR', server['status'])
# Check the fault message contains the libvirt error
self.assertIn('Exceeded maximum number of retries',
server['fault']['message'])
# self.assertIn('Exceeded maximum number of retries',
# server['fault']['message'])
# Check the logs for the exception
self.assertIn("Missing required attribute 'iothread'",
self.stdlog.logger.output)
self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
# self.assertIn("Missing required attribute 'iothread'",
# self.stdlog.logger.output)
# self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
# Get source XML and verify pinning matches cpu_shared_set
conn = self.host.driver._host.get_connection()
dom = conn.lookupByUUIDString(server['id'])
srv_xml = dom.XMLDesc(0)
srv_emulatorpin = self._get_xml_element(
srv_xml, './cputune/emulatorpin')
srv_iothreadpin = self._get_xml_element(
srv_xml, './cputune/iothreadpin')
self.assertIsNotNone(srv_emulatorpin)
self.assertIsNotNone(srv_iothreadpin)
def test_iothread_pinning_explicit_numa(self):
"""Test iothread pinning with explicit multi-node NUMA topology."""
@@ -120,46 +124,46 @@ class TestIOThreadPinningPinnedCPU(
flavor = self._create_flavor(vcpu=4, extra_spec=extra_spec)
# Server should go ACTIVE
# server = self._create_server(
# flavor_id=flavor, host='host1', networks='none',
# expected_state='ACTIVE')
server = self._create_server(
flavor_id=flavor, host='host1', networks='none',
expected_state='ERROR')
expected_state='ACTIVE')
# server = self._create_server(
# flavor_id=flavor, host='host1', networks='none',
# expected_state='ERROR')
# conn = self.host.driver._host.get_connection()
# dom = conn.lookupByUUIDString(server['id'])
# srv_xml = dom.XMLDesc(0)
conn = self.host.driver._host.get_connection()
dom = conn.lookupByUUIDString(server['id'])
srv_xml = dom.XMLDesc(0)
# # Should have iothreads element
# srv_iothread = self._get_xml_element(srv_xml, './iothreads')
# self.assertIsNotNone(srv_iothread)
# self.assertEqual('1', srv_iothread.text)
# Should have iothreads element
srv_iothread = self._get_xml_element(srv_xml, './iothreads')
self.assertIsNotNone(srv_iothread)
self.assertEqual('1', srv_iothread.text)
# # Should have emulatorpin and iothreadpin
# srv_emulatorpin = self._get_xml_element(
# srv_xml, './cputune/emulatorpin')
# srv_iothreadpin = self._get_xml_element(
# srv_xml, './cputune/iothreadpin')
# self.assertIsNotNone(srv_emulatorpin)
# self.assertIsNotNone(srv_iothreadpin)
# Should have emulatorpin and iothreadpin
srv_emulatorpin = self._get_xml_element(
srv_xml, './cputune/emulatorpin')
srv_iothreadpin = self._get_xml_element(
srv_xml, './cputune/iothreadpin')
self.assertIsNotNone(srv_emulatorpin)
self.assertIsNotNone(srv_iothreadpin)
# # iothreadpin should have iothread attribute set to 1
# self.assertEqual('1', srv_iothreadpin.get('iothread'))
# iothreadpin should have iothread attribute set to 1
self.assertEqual('1', srv_iothreadpin.get('iothread'))
# # Both should be pinned to the union of NUMA nodes
# self.assertEqual(srv_emulatorpin.get('cpuset'),
# srv_iothreadpin.get('cpuset'))
# Both should be pinned to the union of NUMA nodes
self.assertEqual(srv_emulatorpin.get('cpuset'),
srv_iothreadpin.get('cpuset'))
self.assertEqual('ERROR', server['status'])
# self.assertEqual('ERROR', server['status'])
# Check the fault message contains the libvirt error
self.assertIn('Exceeded maximum number of retries',
server['fault']['message'])
# Check the logs for the exception
self.assertIn("Missing required attribute 'iothread'",
self.stdlog.logger.output)
self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
# # Check the fault message contains the libvirt error
# self.assertIn('Exceeded maximum number of retries',
# server['fault']['message'])
# # Check the logs for the exception
# self.assertIn("Missing required attribute 'iothread'",
# self.stdlog.logger.output)
# self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
def test_iothread_pinning_isolated_emulator(self):
"""Test iothread pinning with isolated emulator threads policy."""
@@ -174,52 +178,52 @@ class TestIOThreadPinningPinnedCPU(
}
flavor = self._create_flavor(vcpu=2, extra_spec=extra_spec)
server = self._create_server(
flavor_id=flavor, host='host1', networks='none',
expected_state='ERROR')
# Server should go ACTIVE
# server = self._create_server(
# flavor_id=flavor, host='host1', networks='none',
# expected_state='ACTIVE')
# expected_state='ERROR')
# Server should go ACTIVE
server = self._create_server(
flavor_id=flavor, host='host1', networks='none',
expected_state='ACTIVE')
# conn = self.host.driver._host.get_connection()
# dom = conn.lookupByUUIDString(server['id'])
# srv_xml = dom.XMLDesc(0)
conn = self.host.driver._host.get_connection()
dom = conn.lookupByUUIDString(server['id'])
srv_xml = dom.XMLDesc(0)
# # Should have iothreads element
# srv_iothread = self._get_xml_element(srv_xml, './iothreads')
# self.assertIsNotNone(srv_iothread)
# self.assertEqual('1', srv_iothread.text)
# Should have iothreads element
srv_iothread = self._get_xml_element(srv_xml, './iothreads')
self.assertIsNotNone(srv_iothread)
self.assertEqual('1', srv_iothread.text)
# # Should have emulatorpin and iothreadpin
# srv_emulatorpin = self._get_xml_element(
# srv_xml, './cputune/emulatorpin')
# srv_iothreadpin = self._get_xml_element(
# srv_xml, './cputune/iothreadpin')
# self.assertIsNotNone(srv_emulatorpin)
# self.assertIsNotNone(srv_iothreadpin)
# Should have emulatorpin and iothreadpin
srv_emulatorpin = self._get_xml_element(
srv_xml, './cputune/emulatorpin')
srv_iothreadpin = self._get_xml_element(
srv_xml, './cputune/iothreadpin')
self.assertIsNotNone(srv_emulatorpin)
self.assertIsNotNone(srv_iothreadpin)
# # iothreadpin should have iothread attribute set to 1
# self.assertEqual('1', srv_iothreadpin.get('iothread'))
# iothreadpin should have iothread attribute set to 1
self.assertEqual('1', srv_iothreadpin.get('iothread'))
# # Both should be pinned to the same reserved/isolated CPU
# self.assertEqual(srv_emulatorpin.get('cpuset'),
# srv_iothreadpin.get('cpuset'))
# Both should be pinned to the same reserved/isolated CPU
self.assertEqual(srv_emulatorpin.get('cpuset'),
srv_iothreadpin.get('cpuset'))
# # Should be pinned to a single CPU (the reserved one)
# # With vcpu=2 and isolate policy, one extra CPU is reserved
# cpuset = srv_iothreadpin.get('cpuset')
# # The cpuset should be a single CPU from cpu_dedicated_set
# self.assertIsNotNone(cpuset)
self.assertEqual('ERROR', server['status'])
# Should be pinned to a single CPU (the reserved one)
# With vcpu=2 and isolate policy, one extra CPU is reserved
cpuset = srv_iothreadpin.get('cpuset')
# The cpuset should be a single CPU from cpu_dedicated_set
self.assertIsNotNone(cpuset)
# self.assertEqual('ERROR', server['status'])
# Check the fault message contains the libvirt error
self.assertIn('Exceeded maximum number of retries',
server['fault']['message'])
# Check the logs for the exception
self.assertIn("Missing required attribute 'iothread'",
self.stdlog.logger.output)
self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
# # Check the fault message contains the libvirt error
# self.assertIn('Exceeded maximum number of retries',
# server['fault']['message'])
# # Check the logs for the exception
# self.assertIn("Missing required attribute 'iothread'",
# self.stdlog.logger.output)
# self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
def test_iothread_pinning_shared_emulator(self):
"""Test iothread pinning with shared emulator threads policy."""
@@ -234,48 +238,48 @@ class TestIOThreadPinningPinnedCPU(
}
flavor = self._create_flavor(vcpu=2, extra_spec=extra_spec)
server = self._create_server(
flavor_id=flavor, host='host1', networks='none',
expected_state='ERROR')
# Server should go ACTIVE
# server = self._create_server(
# flavor_id=flavor, host='host1', networks='none',
# expected_state='ACTIVE')
# expected_state='ERROR')
# Server should go ACTIVE
server = self._create_server(
flavor_id=flavor, host='host1', networks='none',
expected_state='ACTIVE')
# conn = self.host.driver._host.get_connection()
# dom = conn.lookupByUUIDString(server['id'])
# srv_xml = dom.XMLDesc(0)
conn = self.host.driver._host.get_connection()
dom = conn.lookupByUUIDString(server['id'])
srv_xml = dom.XMLDesc(0)
# # Should have iothreads element
# srv_iothread = self._get_xml_element(srv_xml, './iothreads')
# self.assertIsNotNone(srv_iothread)
# self.assertEqual('1', srv_iothread.text)
# Should have iothreads element
srv_iothread = self._get_xml_element(srv_xml, './iothreads')
self.assertIsNotNone(srv_iothread)
self.assertEqual('1', srv_iothread.text)
# # Should have emulatorpin and iothreadpin
# srv_emulatorpin = self._get_xml_element(
# srv_xml, './cputune/emulatorpin')
# srv_iothreadpin = self._get_xml_element(
# srv_xml, './cputune/iothreadpin')
# self.assertIsNotNone(srv_emulatorpin)
# self.assertIsNotNone(srv_iothreadpin)
# Should have emulatorpin and iothreadpin
srv_emulatorpin = self._get_xml_element(
srv_xml, './cputune/emulatorpin')
srv_iothreadpin = self._get_xml_element(
srv_xml, './cputune/iothreadpin')
self.assertIsNotNone(srv_emulatorpin)
self.assertIsNotNone(srv_iothreadpin)
# # iothreadpin should have iothread attribute set to 1
# self.assertEqual('1', srv_iothreadpin.get('iothread'))
# iothreadpin should have iothread attribute set to 1
self.assertEqual('1', srv_iothreadpin.get('iothread'))
# # Both should be pinned to cpu_shared_set (0-1)
# self.assertEqual(srv_emulatorpin.get('cpuset'),
# srv_iothreadpin.get('cpuset'))
# self.assertEqual('0-1', srv_iothreadpin.get('cpuset'))
# Both should be pinned to cpu_shared_set (0-1)
self.assertEqual(srv_emulatorpin.get('cpuset'),
srv_iothreadpin.get('cpuset'))
self.assertEqual('0-1', srv_iothreadpin.get('cpuset'))
self.assertEqual('ERROR', server['status'])
# self.assertEqual('ERROR', server['status'])
# Check the fault message contains the libvirt error
self.assertIn('Exceeded maximum number of retries',
server['fault']['message'])
# Check the logs for the exception
self.assertIn("Missing required attribute 'iothread'",
self.stdlog.logger.output)
self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
# # Check the fault message contains the libvirt error
# self.assertIn('Exceeded maximum number of retries',
# server['fault']['message'])
# # Check the logs for the exception
# self.assertIn("Missing required attribute 'iothread'",
# self.stdlog.logger.output)
# self.assertIn("element 'iothreadpin'", self.stdlog.logger.output)
def test_iothread_no_pinning(self):
# No CPU pinning (shared CPUs only)
+3 -2
View File
@@ -4054,8 +4054,9 @@ class LibvirtConfigGuestCPUTuneTest(LibvirtConfigBaseTest):
cputune.emulatorpin = emu
iot = config.LibvirtConfigGuestCPUTuneIOThreadPin()
iot.iothread = 1
iot.cpuset = set([0, 1, 2, 3, 4, 5, 6, 7])
cputune.iothreadpin = iot
cputune.iothreadpin.append(iot)
sch0 = config.LibvirtConfigGuestCPUTuneVCPUSched()
sch0.vcpus = set([0, 1, 2, 3])
@@ -4071,7 +4072,7 @@ class LibvirtConfigGuestCPUTuneTest(LibvirtConfigBaseTest):
self.assertXmlEqual("""
<cputune>
<emulatorpin cpuset="0-7"/>
<iothreadpin cpuset="0-7"/>
<iothreadpin iothread="1" cpuset="0-7"/>
<vcpupin vcpu="0" cpuset="0-1"/>
<vcpupin vcpu="1" cpuset="2-3"/>
<vcpupin vcpu="2" cpuset="4-5"/>
+19 -9
View File
@@ -5301,11 +5301,14 @@ class LibvirtConnTestCase(test.NoDBTestCase,
cfg = drvr._get_guest_config(instance_ref, [],
image_meta, disk_info)
self.assertIsInstance(cfg.cputune.iothreadpin,
self.assertIsInstance(cfg.cputune.iothreadpin, list)
self.assertEqual(1, len(cfg.cputune.iothreadpin))
self.assertIsInstance(cfg.cputune.iothreadpin[0],
vconfig.LibvirtConfigGuestCPUTuneIOThreadPin)
self.assertEqual(1, cfg.cputune.iothreadpin[0].iothread)
self.assertEqual(cfg.cputune.emulatorpin.cpuset,
cfg.cputune.iothreadpin.cpuset)
self.assertEqual(set([6]), cfg.cputune.iothreadpin.cpuset)
cfg.cputune.iothreadpin[0].cpuset)
self.assertEqual(set([6]), cfg.cputune.iothreadpin[0].cpuset)
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
def test_get_guest_config_iothreadpin_shared_emulator_threads(self):
@@ -5356,11 +5359,14 @@ class LibvirtConnTestCase(test.NoDBTestCase,
cfg = drvr._get_guest_config(instance_ref, [],
image_meta, disk_info)
self.assertIsInstance(cfg.cputune.iothreadpin,
self.assertIsInstance(cfg.cputune.iothreadpin, list)
self.assertEqual(1, len(cfg.cputune.iothreadpin))
self.assertIsInstance(cfg.cputune.iothreadpin[0],
vconfig.LibvirtConfigGuestCPUTuneIOThreadPin)
self.assertEqual(1, cfg.cputune.iothreadpin[0].iothread)
self.assertEqual(cfg.cputune.emulatorpin.cpuset,
cfg.cputune.iothreadpin.cpuset)
self.assertEqual(set([1, 2]), cfg.cputune.iothreadpin.cpuset)
cfg.cputune.iothreadpin[0].cpuset)
self.assertEqual(set([1, 2]), cfg.cputune.iothreadpin[0].cpuset)
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
def test_get_guest_config_iothreadpin_numa_topology(self):
@@ -5407,11 +5413,15 @@ class LibvirtConnTestCase(test.NoDBTestCase,
cfg = drvr._get_guest_config(instance_ref, [],
image_meta, disk_info)
self.assertIsInstance(cfg.cputune.iothreadpin,
self.assertIsInstance(cfg.cputune.iothreadpin, list)
self.assertEqual(1, len(cfg.cputune.iothreadpin))
self.assertIsInstance(cfg.cputune.iothreadpin[0],
vconfig.LibvirtConfigGuestCPUTuneIOThreadPin)
self.assertEqual(1, cfg.cputune.iothreadpin[0].iothread)
self.assertEqual(cfg.cputune.emulatorpin.cpuset,
cfg.cputune.iothreadpin.cpuset)
self.assertEqual(set([0, 1, 2, 3]), cfg.cputune.iothreadpin.cpuset)
cfg.cputune.iothreadpin[0].cpuset)
self.assertEqual(set([0, 1, 2, 3]),
cfg.cputune.iothreadpin[0].cpuset)
def test_get_guest_config_numa_host_instance_shared_emulthreads_err(
self):
+9 -3
View File
@@ -2675,6 +2675,7 @@ class LibvirtConfigGuestCPUTuneIOThreadPin(LibvirtConfigObject):
**kwargs)
self.cpuset = None
self.iothread = None
def format_dom(self):
root = super(LibvirtConfigGuestCPUTuneIOThreadPin, self).format_dom()
@@ -2682,6 +2683,8 @@ class LibvirtConfigGuestCPUTuneIOThreadPin(LibvirtConfigObject):
if self.cpuset is not None:
root.set("cpuset",
hardware.format_cpu_spec(self.cpuset))
if self.iothread is not None:
root.set("iothread", str(self.iothread))
return root
@@ -2721,7 +2724,7 @@ class LibvirtConfigGuestCPUTune(LibvirtConfigObject):
self.period = None
self.vcpupin = []
self.emulatorpin = None
self.iothreadpin = None
self.iothreadpin = []
self.vcpusched = []
def format_dom(self):
@@ -2736,8 +2739,11 @@ class LibvirtConfigGuestCPUTune(LibvirtConfigObject):
if self.emulatorpin is not None:
root.append(self.emulatorpin.format_dom())
if self.iothreadpin is not None:
root.append(self.iothreadpin.format_dom())
# Only render <iothreadpin> if fully configured to avoid bug #2140537:
# libvirt requires 'iothread' attribute and non-empty 'cpuset'
for pin in self.iothreadpin:
if pin.iothread is not None and pin.cpuset:
root.append(pin.format_dom())
for vcpu in self.vcpupin:
root.append(vcpu.format_dom())
for sched in self.vcpusched:
+14 -6
View File
@@ -6462,6 +6462,9 @@ class LibvirtDriver(driver.ComputeDriver):
return emulatorpin_cpuset
def _get_guest_iothread(self):
return 1
def _get_guest_numa_config(self, instance_numa_topology, flavor,
image_meta):
"""Returns the config objects for the guest NUMA specs.
@@ -6532,9 +6535,6 @@ class LibvirtDriver(driver.ComputeDriver):
guest_cpu_tune.emulatorpin = (
vconfig.LibvirtConfigGuestCPUTuneEmulatorPin())
guest_cpu_tune.emulatorpin.cpuset = set([])
guest_cpu_tune.iothreadpin = (
vconfig.LibvirtConfigGuestCPUTuneIOThreadPin())
guest_cpu_tune.iothreadpin.cpuset = set([])
# Init NUMATune configuration
guest_numa_tune = vconfig.LibvirtConfigGuestNUMATune()
@@ -6578,7 +6578,14 @@ class LibvirtDriver(driver.ComputeDriver):
# both emulator and iothreads are pinned to cores other
# than the instance's cores to support realtime cpus.
guest_cpu_tune.emulatorpin.cpuset.update(emu_pin_cpuset)
guest_cpu_tune.iothreadpin.cpuset.update(emu_pin_cpuset)
# Create iothreadpin entries after processing all cells
# Use the same cpuset as emulatorpin
for iothread_id in range(self._get_guest_iothread()):
iothread_pin = vconfig.LibvirtConfigGuestCPUTuneIOThreadPin()
iothread_pin.iothread = iothread_id + 1
iothread_pin.cpuset = guest_cpu_tune.emulatorpin.cpuset
guest_cpu_tune.iothreadpin.append(iothread_pin)
# TODO(berrange) When the guest has >1 NUMA node, it will
# span multiple host NUMA nodes. By pinning emulator threads
@@ -7609,8 +7616,9 @@ class LibvirtDriver(driver.ComputeDriver):
self._set_features(guest, instance.os_type, image_meta, flavor)
self._set_clock(guest, instance.os_type, image_meta)
# Set IOThreads to 1 for everybody
guest.iothreads = 1
# Set IOThreads to the same value for everybody,
# returned by _get_guest_iothread
guest.iothreads = self._get_guest_iothread()
storage_configs = self._get_guest_storage_config(context,
instance, image_meta, disk_info, rescue, block_device_info,