diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index acb2775d449a..3d245978baaa 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -9736,6 +9736,76 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'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)
+
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
@mock.patch.object(fakelibvirt.virDomain, "XMLDesc")
@@ -9778,6 +9848,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_disks': disk_paths,
'bandwidth': _bandwidth,
'destination_xml': target_xml,
+ 'persistent_xml': target_xml,
}
# start test
@@ -9885,7 +9956,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_disks': disk_paths,
'migrate_uri': 'tcp://127.0.0.2',
'bandwidth': CONF.libvirt.live_migration_bandwidth,
- 'destination_xml': target_xml
+ 'destination_xml': target_xml,
}
# Start test
@@ -9985,6 +10056,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_uri': 'tcp://127.0.0.2',
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'destination_xml': target_xml,
+ 'persistent_xml': target_xml,
}
# start test
@@ -10335,6 +10407,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_disks': ['vda', 'vdb'],
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'destination_xml': target_xml,
+ 'persistent_xml': target_xml,
}
# start test
@@ -10388,14 +10461,14 @@ class LibvirtConnTestCase(test.NoDBTestCase,
@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
target_connection = '127.0.0.2'
# Preparing mocks
disk_paths = ['vda', 'vdb']
@@ -10403,6 +10476,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_uri': 'tcp://127.0.0.2',
'migrate_disks': ['vda', 'vdb'],
'bandwidth': CONF.libvirt.live_migration_bandwidth,
+ 'destination_xml': mock.sentinel.new_xml,
+ 'persistent_xml': mock.sentinel.new_xml,
}
mock_migrateToURI3.side_effect = fakelibvirt.libvirtError("ERR")
@@ -10461,6 +10536,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_disks': device_names,
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'destination_xml': '',
+ 'persistent_xml': '',
}
if not params['migrate_disks']:
del params['migrate_disks']
@@ -10492,14 +10568,14 @@ class LibvirtConnTestCase(test.NoDBTestCase,
@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
target_connection = None
device_names = ['disk1', 'disk2']
@@ -10507,7 +10583,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
# Since we are passing the VIR_MIGRATE_TUNNELLED flag, the
# 'parms' dict will not (as expected) contain 'migrate_disks'
params = {
- 'bandwidth': CONF.libvirt.live_migration_bandwidth
+ 'bandwidth': CONF.libvirt.live_migration_bandwidth,
+ 'destination_xml': mock.sentinel.new_xml,
+ 'persistent_xml': mock.sentinel.new_xml,
}
# Start test
migrate_data = objects.LibvirtLiveMigrateData(
@@ -10554,6 +10632,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'migrate_disks': disk_paths,
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'destination_xml': '',
+ 'persistent_xml': '',
}
# Prepare mocks
diff --git a/nova/tests/unit/virt/test_virt_drivers.py b/nova/tests/unit/virt/test_virt_drivers.py
index 0f1ac970d157..b7ed71f6657f 100644
--- a/nova/tests/unit/virt/test_virt_drivers.py
+++ b/nova/tests/unit/virt/test_virt_drivers.py
@@ -135,7 +135,7 @@ class _FakeDriverBackendTestCase(object):
self.stub_out('nova.virt.libvirt.guest.Guest.migrate',
lambda self, destination, migrate_uri=None,
migrate_disks=None, destination_xml=None, flags=0,
- bandwidth=0: None)
+ bandwidth=0, persistent_xml_param=False: None)
# We can't actually make a config drive v2 because ensure_tree has
# been faked out
self.stub_out('nova.virt.configdrive.ConfigDriveBuilder.make_drive',
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 6a7ae8b1b3d6..abb8753e478d 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -301,6 +301,11 @@ MIN_LIBVIRT_BETTER_SIGKILL_HANDLING = (4, 7, 0)
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 = {
@@ -7279,13 +7284,18 @@ class LibvirtDriver(driver.ComputeDriver):
if CONF.serial_console.enabled:
serial_ports = list(self._get_serial_ports_from_guest(guest))
+ # NOTE(lyarwood): Only available from v1.3.4
+ persistent_xml_param = self._host.has_min_version(
+ MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML)
+
LOG.debug("About to invoke the migrate API", instance=instance)
guest.migrate(self._live_migration_uri(dest),
migrate_uri=migrate_uri,
flags=migration_flags,
migrate_disks=device_names,
destination_xml=new_xml_str,
- bandwidth=CONF.libvirt.live_migration_bandwidth)
+ bandwidth=CONF.libvirt.live_migration_bandwidth,
+ persistent_xml_param=persistent_xml_param)
LOG.debug("Migrate API has completed", instance=instance)
for hostname, port in serial_ports:
diff --git a/nova/virt/libvirt/guest.py b/nova/virt/libvirt/guest.py
index ef015ee8b9b8..847262c03383 100644
--- a/nova/virt/libvirt/guest.py
+++ b/nova/virt/libvirt/guest.py
@@ -610,7 +610,8 @@ class Guest(object):
self._domain.suspend()
def migrate(self, destination, migrate_uri=None, migrate_disks=None,
- destination_xml=None, flags=0, bandwidth=0):
+ destination_xml=None, flags=0, bandwidth=0,
+ persistent_xml_param=False):
"""Migrate guest object from its current host to the destination
:param destination: URI of host destination where guest will be migrate
@@ -645,6 +646,9 @@ class Guest(object):
unsafe.
VIR_MIGRATE_OFFLINE Migrate offline
:param bandwidth: The maximum bandwidth in MiB/s
+ :param persistent_xml_param: Boolean indicating if the
+ VIR_MIGRATE_PARAM_PERSIST_XML param should
+ be provided to migrateToURI3.
"""
params = {}
# In migrateToURI3 these parameters are extracted from the
@@ -653,6 +657,9 @@ class Guest(object):
if destination_xml:
params['destination_xml'] = destination_xml
+ # NOTE(lyarwood): Only available from v1.3.4
+ if persistent_xml_param:
+ params['persistent_xml'] = destination_xml
if migrate_disks:
params['migrate_disks'] = migrate_disks
if migrate_uri: