From 873aee5e95a06987fc1b6a476ce0e81c4722fb2a Mon Sep 17 00:00:00 2001 From: lajoskatona Date: Thu, 5 Feb 2026 19:26:54 +0100 Subject: [PATCH] Fix for bug 2140537 If a guest has pinned CPUs the domain XML's should have iothread attribute also. Closes-Bug: #2140537 Change-Id: I5c2df747a3fdfbd2ee31d50a3d716a0ccc787e15 Signed-off-by: lajoskatona --- nova/tests/fixtures/libvirt.py | 29 +-- .../regressions/test_bug_2140537.py | 240 +++++++++--------- nova/tests/unit/virt/libvirt/test_config.py | 5 +- nova/tests/unit/virt/libvirt/test_driver.py | 28 +- nova/virt/libvirt/config.py | 12 +- nova/virt/libvirt/driver.py | 20 +- 6 files changed, 175 insertions(+), 159 deletions(-) diff --git a/nova/tests/fixtures/libvirt.py b/nova/tests/fixtures/libvirt.py index 2202a80525..3a75459138 100644 --- a/nova/tests/fixtures/libvirt.py +++ b/nova/tests/fixtures/libvirt.py @@ -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 diff --git a/nova/tests/functional/regressions/test_bug_2140537.py b/nova/tests/functional/regressions/test_bug_2140537.py index f7be2b0d4f..13e48b23cd 100644 --- a/nova/tests/functional/regressions/test_bug_2140537.py +++ b/nova/tests/functional/regressions/test_bug_2140537.py @@ -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) diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py index 4de39628cb..83bb2bab08 100644 --- a/nova/tests/unit/virt/libvirt/test_config.py +++ b/nova/tests/unit/virt/libvirt/test_config.py @@ -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(""" - + diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index a3568d3d2d..5464365fed 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -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): diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py index 849a1c7f6b..0ee794d422 100644 --- a/nova/virt/libvirt/config.py +++ b/nova/virt/libvirt/config.py @@ -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 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: diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 3c5de7aecd..3428d7eb37 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -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,