From ea9bf5216be60fb3fa1704cf6257adca4e634cb1 Mon Sep 17 00:00:00 2001 From: Matthew Booth Date: Mon, 25 Sep 2017 17:32:14 +0100 Subject: [PATCH] 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. Change-Id: I9b545ca8aa6dd7b41ddea2d333190c9fbed19bc1 Resolves-bug: #1719362 --- nova/tests/unit/virt/libvirt/test_driver.py | 62 ++++++++++++++++++++- nova/virt/libvirt/guest.py | 12 ++++ 2 files changed, 73 insertions(+), 1 deletion(-) mode change 100644 => 100755 nova/tests/unit/virt/libvirt/test_driver.py mode change 100644 => 100755 nova/virt/libvirt/guest.py diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py old mode 100644 new mode 100755 index 01b3213d95..6415e8cbbb --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -8587,6 +8587,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='') + 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': '', + } + 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', @@ -8619,9 +8674,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. diff --git a/nova/virt/libvirt/guest.py b/nova/virt/libvirt/guest.py old mode 100644 new mode 100755 index 5db89fe564..4a1819aef1 --- a/nova/virt/libvirt/guest.py +++ b/nova/virt/libvirt/guest.py @@ -640,6 +640,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: