Merge "Fix: migration configuration with cpu_shared_set (libvirt part)"

This commit is contained in:
Zuul 2024-04-24 20:37:35 +00:00 committed by Gerrit Code Review
commit ca1db54f1b
5 changed files with 162 additions and 13 deletions

View File

@ -261,9 +261,9 @@ class LiveMigrationWithCpuSharedSet(
conn = self.dest.driver._host.get_connection()
dom = conn.lookupByUUIDString(self.server['id'])
xml = dom.XMLDesc(0)
# The destination should be updated to "3-4" but it is not the case.
self.assertIn('<vcpu cpuset="0-1">1</vcpu>', xml)
self.assertNotIn('<vcpu cpuset="3-4">1</vcpu>', xml)
# The destination should be updated to "3-4".
self.assertNotIn('<vcpu cpuset="0-1">1</vcpu>', xml)
self.assertIn('<vcpu cpuset="3-4">1</vcpu>', xml)
def test_live_migration_to_no_cpu_shared_set(self):
"""Reproducer for bug 1869804 #2.
@ -302,10 +302,9 @@ class LiveMigrationWithCpuSharedSet(
dom = conn.lookupByUUIDString(self.server['id'])
xml = dom.XMLDesc(0)
# The destination cpuset should be removed because the
# host has no cpu_shared_set configured. Which is not the case due to
# the bug.
self.assertIn('<vcpu cpuset="0-1">1</vcpu>', xml)
self.assertNotIn('<vcpu>1</vcpu>', xml)
# host has no cpu_shared_set configured.
self.assertNotIn('<vcpu cpuset="0-1">1</vcpu>', xml)
self.assertIn('<vcpu>1</vcpu>', xml)
def test_live_migration_from_no_cpu_shared_set_to_cpu_shared_set(self):
"""Reproducer for bug 1869804 #3.
@ -329,5 +328,5 @@ class LiveMigrationWithCpuSharedSet(
dom = conn.lookupByUUIDString(self.server['id'])
xml = dom.XMLDesc(0)
# The destination should be updated to "0-1".
self.assertIn('<vcpu>1</vcpu>', xml)
self.assertNotIn('<vcpu cpuset="0-1">1</vcpu>', xml)
self.assertNotIn('<vcpu>1</vcpu>', xml)
self.assertIn('<vcpu cpuset="0-1">1</vcpu>', xml)

View File

@ -11400,6 +11400,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'dst_wants_file_backed_memory': False,
'graphics_listen_addr_spice': '127.0.0.1',
'graphics_listen_addr_vnc': '127.0.0.1',
'dst_cpu_shared_set_info': (),
'serial_listen_addr': None},
return_value.obj_to_primitive()['nova_object.data'])
@ -11440,6 +11441,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'dst_wants_file_backed_memory': False,
'graphics_listen_addr_spice': '127.0.0.1',
'graphics_listen_addr_vnc': '127.0.0.1',
'dst_cpu_shared_set_info': (),
'serial_listen_addr': None},
return_value.obj_to_primitive()['nova_object.data'])
@ -11477,6 +11479,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'dst_wants_file_backed_memory': False,
'graphics_listen_addr_spice': '127.0.0.1',
'graphics_listen_addr_vnc': '127.0.0.1',
'dst_cpu_shared_set_info': (),
'serial_listen_addr': None},
return_value.obj_to_primitive()['nova_object.data'])
@ -11563,6 +11566,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'dst_wants_file_backed_memory': False,
'graphics_listen_addr_spice': '127.0.0.1',
'graphics_listen_addr_vnc': '127.0.0.1',
'dst_cpu_shared_set_info': (),
'serial_listen_addr': None},
result.obj_to_primitive()['nova_object.data'])
@ -11703,6 +11707,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'dst_wants_file_backed_memory': False,
'graphics_listen_addr_spice': '127.0.0.1',
'graphics_listen_addr_vnc': '127.0.0.1',
'dst_cpu_shared_set_info': (),
'serial_listen_addr': None},
return_value.obj_to_primitive()['nova_object.data'])

View File

@ -68,15 +68,18 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
'spice': None}, addrs)
@mock.patch('lxml.etree.tostring')
@mock.patch.object(migration, '_remove_cpu_shared_set_xml')
@mock.patch.object(migration, '_update_cpu_shared_set_xml')
@mock.patch.object(migration, '_update_memory_backing_xml')
@mock.patch.object(migration, '_update_perf_events_xml')
@mock.patch.object(migration, '_update_graphics_xml')
@mock.patch.object(migration, '_update_serial_xml')
@mock.patch.object(migration, '_update_volume_xml')
def test_get_updated_guest_xml(
self, mock_volume, mock_serial, mock_graphics,
mock_perf_events_xml, mock_memory_backing, mock_tostring):
data = objects.LibvirtLiveMigrateData()
def _test_get_updated_guest_xml(
self, data, check_cpu_shared_set, mock_volume, mock_serial,
mock_graphics, mock_perf_events_xml, mock_memory_backing,
mock_update_cpu_shared_set, mock_remove_cpu_shared_set, mock_tostring
):
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
get_volume_config = mock.MagicMock()
mock_guest.get_xml_desc.return_value = '<domain></domain>'
@ -91,6 +94,57 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
mock_perf_events_xml.assert_called_once_with(mock.ANY, data)
mock_memory_backing.assert_called_once_with(mock.ANY, data)
self.assertEqual(1, mock_tostring.called)
if check_cpu_shared_set is not None:
check_cpu_shared_set(
mock_update_cpu_shared_set, mock_remove_cpu_shared_set)
def test_get_updated_guest_xml(self):
data = objects.LibvirtLiveMigrateData()
self._test_get_updated_guest_xml(data, None)
def test_get_updated_guest_xml_dst_cpu_shared_set_info_unset(self):
data = objects.LibvirtLiveMigrateData()
def check_cpu_shared_set(
mock_update_cpu_shared_set, mock_remove_cpu_shared_set
):
mock_update_cpu_shared_set.assert_not_called()
mock_remove_cpu_shared_set.assert_not_called()
self._test_get_updated_guest_xml(data, check_cpu_shared_set)
def test_get_updated_guest_xml_dst_cpu_shared_set_info_set_to_none(self):
"""Ensure this test fails as dst_cpu_shared_set_info cannot be None
"""
def set_dst_cpu_shared_set_info():
data.dst_cpu_shared_set_info = None
data = objects.LibvirtLiveMigrateData()
self.assertRaises(ValueError, set_dst_cpu_shared_set_info)
def test_get_updated_guest_xml_dst_cpu_shared_set_info_set_to_empty(self):
data = objects.LibvirtLiveMigrateData()
data.dst_cpu_shared_set_info = set()
def check_cpu_shared_set(
mock_update_cpu_shared_set, mock_remove_cpu_shared_set
):
mock_update_cpu_shared_set.assert_not_called()
mock_remove_cpu_shared_set.assert_called_once_with(mock.ANY, data)
self._test_get_updated_guest_xml(data, check_cpu_shared_set)
def test_get_updated_guest_xml_dst_cpu_shared_set_info_set(self):
data = objects.LibvirtLiveMigrateData()
data.dst_cpu_shared_set_info = set([2, 3])
def check_cpu_shared_set(
mock_update_cpu_shared_set, mock_remove_cpu_shared_set
):
mock_update_cpu_shared_set.assert_called_once_with(mock.ANY, data)
mock_remove_cpu_shared_set.assert_not_called()
self._test_get_updated_guest_xml(data, check_cpu_shared_set)
def test_update_quota_xml(self):
old_xml = """<domain>
@ -224,6 +278,44 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
self.assertRaises(exception.NovaException,
migration._update_mdev_xml, doc, data.target_mdevs)
def test_update_cpu_shared_set_xml(self):
doc = etree.fromstring("""
<domain>
<vcpu cpuset="0-1">1</vcpu>
</domain>""")
data = objects.LibvirtLiveMigrateData(
dst_cpu_shared_set_info=set([3, 4]))
result = etree.tostring(
migration._update_cpu_shared_set_xml(copy.deepcopy(doc), data),
encoding='unicode')
expected = textwrap.dedent("""
<domain>
<vcpu cpuset="3-4">1</vcpu>
</domain>""")
self.assertXmlEqual(expected, result)
def test_remove_cpu_shared_set_xml(self):
doc = etree.fromstring("""
<domain>
<vcpu cpuset="0-1">1</vcpu>
</domain>""")
data = objects.LibvirtLiveMigrateData(
dst_cpu_shared_set_info=set())
result = etree.tostring(
migration._remove_cpu_shared_set_xml(copy.deepcopy(doc), data),
encoding='unicode')
expected = textwrap.dedent("""
<domain>
<vcpu>1</vcpu>
</domain>""")
self.assertXmlEqual(expected, result)
def test_update_numa_xml(self):
doc = etree.fromstring("""
<domain>

View File

@ -10007,6 +10007,12 @@ class LibvirtDriver(driver.ComputeDriver):
if instance.numa_topology:
data.dst_supports_numa_live_migration = True
data.dst_cpu_shared_set_info = (
hardware.get_cpu_shared_set() or
hardware.get_vcpu_pin_set() or
set()
)
# NOTE(sean-k-mooney): The migrate_data vifs field is used to signal
# that we are using the multiple port binding workflow so we can only
# populate it if we are using multiple port bindings.

View File

@ -65,6 +65,25 @@ def get_updated_guest_xml(instance, guest, migrate_data, get_volume_config,
xml_doc = _update_quota_xml(instance, xml_doc)
if get_vif_config is not None:
xml_doc = _update_vif_xml(xml_doc, migrate_data, get_vif_config)
# If 'dst_cpu_shared_set_info' is set, we are migrating a VM to a
# destination host, patched to fix bug 1869804.
# Then, if dst_cpu_shared_set_info is empty (set()), it means that there
# is no cpu_shared_set configuration on the destination host.
if (
'dst_cpu_shared_set_info' in migrate_data and
not migrate_data.dst_cpu_shared_set_info
):
# There is no cpu_shared_set configured on destination host. So we
# need to remove the VM cpuset if any.
xml_doc = _remove_cpu_shared_set_xml(xml_doc, migrate_data)
if (
'dst_cpu_shared_set_info' in migrate_data and
migrate_data.dst_cpu_shared_set_info
):
# There is cpu_shared_set configured on destination host. So we need
# to update the VM cpuset.
xml_doc = _update_cpu_shared_set_xml(xml_doc, migrate_data)
if 'dst_numa_info' in migrate_data:
xml_doc = _update_numa_xml(xml_doc, migrate_data)
if 'target_mdevs' in migrate_data:
@ -130,6 +149,34 @@ def _update_mdev_xml(xml_doc, target_mdevs):
return xml_doc
def _update_cpu_shared_set_xml(xml_doc, migrate_data):
LOG.debug('_update_cpu_shared_set_xml input xml=%s',
etree.tostring(xml_doc, encoding='unicode', pretty_print=True))
vcpu = xml_doc.find('./vcpu')
vcpu.set('cpuset', hardware.format_cpu_spec(
migrate_data.dst_cpu_shared_set_info, True))
LOG.debug('_update_cpu_shared_set_xml output xml=%s',
etree.tostring(xml_doc, encoding='unicode', pretty_print=True))
return xml_doc
def _remove_cpu_shared_set_xml(xml_doc, migrate_data):
LOG.debug('_remove_cpu_shared_set_xml input xml=%s',
etree.tostring(xml_doc, encoding='unicode', pretty_print=True))
vcpu = xml_doc.find('./vcpu')
if vcpu is not None:
cpuset = vcpu.get('cpuset')
if cpuset:
del vcpu.attrib['cpuset']
LOG.debug('_remove_cpu_shared_set_xml output xml=%s',
etree.tostring(xml_doc, encoding='unicode', pretty_print=True))
return xml_doc
def _update_numa_xml(xml_doc, migrate_data):
LOG.debug('_update_numa_xml input xml=%s',
etree.tostring(xml_doc, encoding='unicode', pretty_print=True))