Browse Source

Handle multiple 'vcpusched' elements during live migrate

When live migrating a pinned instance, we recalculate pinning
information for the destination host and then update the instance's XML
before spawning the instance there. As part of the pinning information
recalculation, we must also recalculate information for realtime cores,
which are configured using the '<vcpusched>' element. The
'nova.virt.libvirt.migration._update_numa_xml' function, which handles
this updating, was assuming there would only be one of these elements.
This is a reasonably sane assumption since this is all we create in the
'nova.virt.libvirt.LibvirtDriver._get_guest_numa_config' function used
to generate the initial instance XML. However, a look at logs show that
at least some (all?) versions of libvirt actually rewrite the XML we're
providing them. Compare what is returned from '_get_guest_xml':

  DEBUG nova.virt.libvirt.driver [...] [instance: ...] End _get_guest_xml xml=<domain type="kvm">
      <vcpusched vcpus="2-3" scheduler="fifo" priority="1"/>
   {{(pid=12600) _get_guest_xml /opt/stack/nova/nova/virt/libvirt/}}

to what is seen when we enter '_update_numa_xml' (or via 'virsh dumpxml'
at any point after instance creation):

  DEBUG nova.virt.libvirt.migration [-] _update_numa_xml input xml=<domain type="kvm">
      <vcpusched vcpus="2" scheduler="fifo" priority="1"/>
      <vcpusched vcpus="3" scheduler="fifo" priority="1"/>
   {{(pid=12600) _update_numa_xml /opt/stack/nova/nova/virt/libvirt/}

The solution is simple: rather than trying to modify the existing XML,
simply scrap it and rebuild the elements from scratch. We should
probably do this for all elements, but that can/should be tackled

Change-Id: Ic01603a91f6099f1068af0e955f3e1056021d673
Signed-off-by: Stephen Finucane <>
Closes-Bug: #1889257
(cherry picked from commit b6aef1ec4f)
Stephen Finucane 2 weeks ago
2 changed files with 53 additions and 32 deletions
  1. +36
  2. +17

+ 36
- 29
nova/tests/unit/virt/libvirt/ View File

@@ -181,42 +181,49 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
self.assertXmlEqual(res, new_xml)

def test_update_numa_xml(self):
xml = textwrap.dedent("""
doc = etree.fromstring("""
<vcpupin vcpu="0" cpuset="0,1,2,^2"/>
<vcpupin vcpu="1" cpuset="2-4,^4"/>
<emulatorpin cpuset="8-10,^8"/>
<vcpusched vcpus="10-12,^12" priority="13"/>
<memory nodeset="4,5,6,7"/>
<memnode cellid="2" nodeset="4-6,^6"/>
<memnode cellid="3" nodeset="6-8,^8"/>
<vcpupin vcpu="0" cpuset="0,1,2,^2"/>
<vcpupin vcpu="1" cpuset="2-4,^4"/>
<emulatorpin cpuset="8-10,^8"/>
<vcpusched vcpus="10" priority="13" scheduler="fifo"/>
<vcpusched vcpus="11" priority="13" scheduler="fifo"/>
<memory nodeset="4,5,6,7"/>
<memnode cellid="2" nodeset="4-6,^6"/>
<memnode cellid="3" nodeset="6-8,^8"/>
doc = etree.fromstring(xml)
data = objects.LibvirtLiveMigrateData(
cpu_pins={'0': set([10, 11]),
'1': set([12, 13])},
cell_pins={'2': set([14, 15]),
'3': set([16, 17])},
cpu_pins={'0': set([10, 11]), '1': set([12, 13])},
cell_pins={'2': set([14, 15]), '3': set([16, 17])},
emulator_pins=set([18, 19]),
sched_vcpus=set([20, 21]),
res = etree.tostring(migration._update_numa_xml(copy.deepcopy(doc),
doc.find('./cputune/vcpupin/[@vcpu="0"]').set('cpuset', '10-11')
doc.find('./cputune/vcpupin/[@vcpu="1"]').set('cpuset', '12-13')
doc.find('./cputune/emulatorpin').set('cpuset', '18-19')
doc.find('./cputune/vcpusched').set('vcpus', '20-21')
doc.find('./cputune/vcpusched').set('priority', '22')
doc.find('./numatune/memory').set('nodeset', '14-17')
doc.find('./numatune/memnode/[@cellid="2"]').set('nodeset', '14-15')
doc.find('./numatune/memnode/[@cellid="3"]').set('nodeset', '16-17')
self.assertXmlEqual(res, etree.tostring(doc, encoding='unicode'))

result = etree.tostring(
migration._update_numa_xml(copy.deepcopy(doc), data),

expected = textwrap.dedent("""
<vcpupin vcpu="0" cpuset="10-11"/>
<vcpupin vcpu="1" cpuset="12-13"/>
<emulatorpin cpuset="18-19"/>
<vcpusched vcpus="20-21" priority="22" scheduler="fifo"/>
<memory nodeset="14-17"/>
<memnode cellid="2" nodeset="14-15"/>
<memnode cellid="3" nodeset="16-17"/>

self.assertXmlEqual(expected, result)

def test_update_numa_xml_no_updates(self):
xml = textwrap.dedent("""

+ 17
- 3
nova/virt/libvirt/ View File

@@ -150,9 +150,23 @@ def _update_numa_xml(xml_doc, migrate_data):
memory.set('nodeset', hardware.format_cpu_spec(set(all_cells)))

if 'sched_vcpus' and 'sched_priority' in info:
vcpusched = xml_doc.find('./cputune/vcpusched')
vcpusched.set('vcpus', hardware.format_cpu_spec(info.sched_vcpus))
vcpusched.set('priority', str(info.sched_priority))
cputune = xml_doc.find('./cputune')

# delete the old variant(s)
for elem in cputune.findall('./vcpusched'):

# ...and create a new, shiny one
vcpusched = vconfig.LibvirtConfigGuestCPUTuneVCPUSched()
vcpusched.vcpus = info.sched_vcpus
vcpusched.priority = info.sched_priority
# TODO(stephenfin): Stop assuming scheduler type. We currently only
# create these elements for real-time instances and 'fifo' is the only
# scheduler policy we currently support so this is reasonably safe to
# assume, but it's still unnecessary
vcpusched.scheduler = 'fifo'


LOG.debug('_update_numa_xml output xml=%s',
etree.tostring(xml_doc, encoding='unicode', pretty_print=True))