diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 69d06388312..e5c85a54aa5 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -9160,6 +9160,76 @@ def test_is_shared_block_storage_nfs(self):
'instance', data, block_device_info=bdi))
self.assertEqual(0, mock_get_instance_disk_info.call_count)
+ @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
+ @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
+ @mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
+ def test_live_migration_persistent_xml(
+ self, mock_get_version, mock_get_updated_xml, mock_migrateToURI3):
+ """Assert that persistent_xml only provided when libvirt is >= v1.3.4
+ """
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
+ instance = self.test_instance
+ dest = '127.0.0.1'
+ block_migration = False
+ migrate_data = objects.LibvirtLiveMigrateData(
+ graphics_listen_addr_vnc='10.0.0.1',
+ graphics_listen_addr_spice='10.0.0.2',
+ serial_listen_addr='127.0.0.1',
+ target_connect_addr='127.0.0.1',
+ bdms=[],
+ block_migration=block_migration)
+ guest = libvirt_guest.Guest(fakelibvirt.virDomain)
+ device_names = ['vda']
+
+ mock_get_updated_xml.return_value = mock.sentinel.dest_xml
+
+ # persistent_xml was introduced in v1.3.4 so provide v1.3.3
+ v1_3_3 = versionutils.convert_version_to_int((1, 3, 3))
+ mock_get_version.return_value = v1_3_3
+
+ drvr._live_migration_operation(
+ self.context, instance, dest, block_migration, migrate_data,
+ guest, device_names)
+
+ expected_uri = drvr._live_migration_uri(dest)
+ expected_flags = 0
+ expected_params = {
+ 'bandwidth': 0,
+ 'destination_xml': mock.sentinel.dest_xml,
+ 'migrate_disks': device_names,
+ 'migrate_uri': 'tcp://127.0.0.1'
+ }
+
+ # Assert that migrateToURI3 is called without the persistent_xml param
+ mock_get_version.assert_called()
+ mock_migrateToURI3.assert_called_once_with(
+ expected_uri, params=expected_params, flags=expected_flags)
+
+ # reset mocks and try again with v1.3.4
+ mock_get_version.reset_mock()
+ mock_migrateToURI3.reset_mock()
+
+ # persistent_xml was introduced in v1.3.4 so provide it this time
+ v1_3_4 = versionutils.convert_version_to_int((1, 3, 4))
+ mock_get_version.return_value = v1_3_4
+
+ drvr._live_migration_operation(
+ self.context, instance, dest,
+ block_migration, migrate_data, guest, device_names)
+
+ expected_params = {
+ 'bandwidth': 0,
+ 'destination_xml': mock.sentinel.dest_xml,
+ 'persistent_xml': mock.sentinel.dest_xml,
+ 'migrate_disks': device_names,
+ 'migrate_uri': 'tcp://127.0.0.1'
+ }
+
+ # Assert that migrateToURI3 is called with the persistent_xml param
+ mock_get_version.assert_called()
+ mock_migrateToURI3.assert_called_once_with(
+ expected_uri, params=expected_params, flags=expected_flags)
+
def test_live_migration_update_graphics_xml(self):
self.compute = manager.ComputeManager()
instance_dict = dict(self.test_instance)
@@ -9731,19 +9801,21 @@ def test_live_migration_fails_without_serial_console_address(self):
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
- @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
- return_value='')
+ @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc',
return_value='')
def test_live_migration_uses_migrateToURI3(
self, mock_old_xml, mock_new_xml, mock_migrateToURI3,
mock_min_version):
+
+ mock_new_xml.return_value = mock.sentinel.new_xml
# Preparing mocks
disk_paths = ['vda', 'vdb']
params = {
'migrate_disks': ['vda', 'vdb'],
'bandwidth': CONF.libvirt.live_migration_bandwidth,
- 'destination_xml': '',
+ 'destination_xml': mock.sentinel.new_xml,
+ 'persistent_xml': mock.sentinel.new_xml,
}
mock_migrateToURI3.side_effect = fakelibvirt.libvirtError("ERR")
@@ -9798,6 +9870,7 @@ def _test_live_migration_block_migration_flags(self,
'migrate_disks': device_names,
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'destination_xml': '',
+ 'persistent_xml': '',
}
mock_migrateToURI3.assert_called_once_with(
drvr._live_migration_uri('dest'), params=params,
@@ -9826,18 +9899,21 @@ def test_live_migration_block_migration_all_filtered(self):
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
- @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml',
- return_value='')
+ @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml')
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='')
def test_block_live_migration_tunnelled_migrateToURI3(
self, mock_old_xml, mock_new_xml,
mock_migrateToURI3, mock_min_version):
self.flags(live_migration_tunnelled=True, group='libvirt')
+
+ mock_new_xml.return_value = mock.sentinel.new_xml
+
# Preparing mocks
disk_paths = []
params = {
'bandwidth': CONF.libvirt.live_migration_bandwidth,
- 'destination_xml': '',
+ 'destination_xml': mock.sentinel.new_xml,
+ 'persistent_xml': mock.sentinel.new_xml,
}
# Start test
migrate_data = objects.LibvirtLiveMigrateData(
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 382e41f2c9d..dfbf02070cd 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -328,6 +328,11 @@ def repr_method(self):
VGPU_RESOURCE_SEMAPHORE = "vgpu_resources"
+# libvirt >= v1.3.4 introduced VIR_MIGRATE_PARAM_PERSIST_XML that needs to be
+# provided when the VIR_MIGRATE_PERSIST_DEST flag is used to ensure the updated
+# domain XML is persisted on the destination.
+MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4)
+
class LibvirtDriver(driver.ComputeDriver):
capabilities = {
@@ -7138,6 +7143,12 @@ def _live_migration_operation(self, context, instance, dest,
libvirt.VIR_MIGRATE_TUNNELLED != 0):
params.pop('migrate_disks')
+ # NOTE(lyarwood): Only available from v1.3.4
+ if self._host.has_min_version(
+ MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML
+ ):
+ params['persistent_xml'] = new_xml_str
+
# TODO(sahid): This should be in
# post_live_migration_at_source but no way to retrieve
# ports acquired on the host for the guest at this