libvirt: Don't VIR_MIGRATE_NON_SHARED_INC without migrate_disks

If we specify block migration, but there are no disks which actually
require block migration we call libvirt's migrateToURI3() with
VIR_MIGRATE_NON_SHARED_INC in flags and an empty migrate_disks in
params. Libvirt interprets this to be the default block migration
behaviour of "block migrate all writeable disks". However,
migrate_disks may only be empty because we filtered attached volumes
out of it, in which case libvirt will block migrate attached volumes.
This is a data corruptor.

This change addresses the issue at the point we call migrateToURI3().
As we never want the default block migration behaviour, we can safely
remove the flag if the list of disks to migrate is empty.

(cherry picked from commit ea9bf5216b)

nova/tests/unit/virt/libvirt/test_driver.py:
  Explicitly asserts byte string destination_xml in
  _test_live_migration_block_migration_flags. Not required in master
  due to change I85cd9a90.

(cherry picked from commit 2486f34ec4)

Change-Id: I9b545ca8aa6dd7b41ddea2d333190c9fbed19bc1
Resolves-bug: #1719362
This commit is contained in:
Matthew Booth 2017-09-25 17:32:14 +01:00 committed by Matt Riedemann
parent 125dd1f30f
commit 2cfdb9c056
2 changed files with 73 additions and 1 deletions

62
nova/tests/unit/virt/libvirt/test_driver.py Normal file → Executable file
View File

@ -8275,6 +8275,61 @@ class LibvirtConnTestCase(test.NoDBTestCase):
drvr._live_migration_uri('dest'),
params=params, flags=0)
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc',
return_value='<xml/>')
def _test_live_migration_block_migration_flags(self,
device_names, expected_flags,
mock_old_xml, mock_min_version, mock_migrateToURI3):
migrate_data = objects.LibvirtLiveMigrateData(
graphics_listen_addr_vnc='0.0.0.0',
graphics_listen_addr_spice='0.0.0.0',
serial_listen_addr='127.0.0.1',
target_connect_addr=None,
bdms=[],
block_migration=True)
dom = fakelibvirt.virDomain
guest = libvirt_guest.Guest(dom)
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
drvr._parse_migration_flags()
instance = objects.Instance(**self.test_instance)
drvr._live_migration_operation(self.context, instance, 'dest',
True, migrate_data, guest,
device_names)
params = {
'migrate_disks': device_names,
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'destination_xml': b'<xml/>',
}
mock_migrateToURI3.assert_called_once_with(
drvr._live_migration_uri('dest'), params=params,
flags=expected_flags)
def test_live_migration_block_migration_with_devices(self):
device_names = ['vda']
expected_flags = (fakelibvirt.VIR_MIGRATE_NON_SHARED_INC |
fakelibvirt.VIR_MIGRATE_UNDEFINE_SOURCE |
fakelibvirt.VIR_MIGRATE_PERSIST_DEST |
fakelibvirt.VIR_MIGRATE_PEER2PEER |
fakelibvirt.VIR_MIGRATE_LIVE)
self._test_live_migration_block_migration_flags(device_names,
expected_flags)
def test_live_migration_block_migration_all_filtered(self):
device_names = []
expected_flags = (fakelibvirt.VIR_MIGRATE_UNDEFINE_SOURCE |
fakelibvirt.VIR_MIGRATE_PERSIST_DEST |
fakelibvirt.VIR_MIGRATE_PEER2PEER |
fakelibvirt.VIR_MIGRATE_LIVE)
self._test_live_migration_block_migration_flags(device_names,
expected_flags)
@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',
@ -8307,9 +8362,14 @@ class LibvirtConnTestCase(test.NoDBTestCase):
instance = objects.Instance(**self.test_instance)
drvr._live_migration_operation(self.context, instance, 'dest',
True, migrate_data, guest, disk_paths)
expected_flags = (fakelibvirt.VIR_MIGRATE_UNDEFINE_SOURCE |
fakelibvirt.VIR_MIGRATE_PERSIST_DEST |
fakelibvirt.VIR_MIGRATE_TUNNELLED |
fakelibvirt.VIR_MIGRATE_PEER2PEER |
fakelibvirt.VIR_MIGRATE_LIVE)
mock_migrateToURI3.assert_called_once_with(
drvr._live_migration_uri('dest'),
params=params, flags=159)
params=params, flags=expected_flags)
def test_live_migration_raises_exception(self):
# Confirms recover method is called when exceptions are raised.

12
nova/virt/libvirt/guest.py Normal file → Executable file
View File

@ -641,6 +641,18 @@ class Guest(object):
destination, flags=flags, bandwidth=bandwidth)
else:
if params:
# Due to a quirk in the libvirt python bindings,
# VIR_MIGRATE_NON_SHARED_INC with an empty migrate_disks is
# interpreted as "block migrate all writable disks" rather than
# "don't block migrate any disks". This includes attached
# volumes, which will potentially corrupt data on those
# volumes. Consequently we need to explicitly unset
# VIR_MIGRATE_NON_SHARED_INC if there are no disks to be block
# migrated.
if (flags & libvirt.VIR_MIGRATE_NON_SHARED_INC != 0 and
not params.get('migrate_disks')):
flags &= ~libvirt.VIR_MIGRATE_NON_SHARED_INC
# In migrateToURI3 these parameters are extracted from the
# `params` dict
if migrate_uri: