From 3d83c462bc87d41995e55b816619c74ad39237fe Mon Sep 17 00:00:00 2001 From: Luis Tomas Date: Mon, 13 Jun 2016 16:42:11 +0200 Subject: [PATCH] Add new configuration option to turn postcopy on/off Since QEMU 2.5 and Libvirt 1.3.3 it is possible to use postcopy live migration to ensure an upper limit in the amount of memory to be transferred duing the live migration process. This option enables to live migrate instances with memory-intensive workloads by driving the migration process by the destination host, i.e., after the switch to postcopy mode, the destination VM is the active one and therefore any dirty pages are directly generated at the destination hosts. Partially-implements: blueprint auto-live-migration-completion Change-Id: I35b8400604eece5ddfba3e35015bcd3500d9f605 Signed-off-by: Luis Tomas Co-Authored-By: Pawel Koniszewski --- nova/conf/libvirt.py | 20 ++++++ nova/tests/unit/virt/libvirt/fakelibvirt.py | 1 + nova/tests/unit/virt/libvirt/test_driver.py | 69 +++++++++++++++++++ nova/virt/libvirt/driver.py | 29 ++++++++ ...completion-post-copy-a7a3a986961c93d8.yaml | 6 ++ 5 files changed, 125 insertions(+) create mode 100644 releasenotes/notes/automatic-live-migration-completion-post-copy-a7a3a986961c93d8.yaml diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py index 1dfd4b239222..4df0733213ae 100644 --- a/nova/conf/libvirt.py +++ b/nova/conf/libvirt.py @@ -152,6 +152,26 @@ libvirt_general_opts = [ help='Time to wait, in seconds, for migration to make forward ' 'progress in transferring data before aborting the ' 'operation. Set to 0 to disable timeouts.'), + cfg.BoolOpt('live_migration_permit_post_copy', + default=False, + help=""" +This option allows nova to switch an on-going live migration to post-copy +mode, i.e., switch the active VM to the one on the destination node before the +migration is complete, therefore ensuring an upper bound on the memory that +needs to be transferred. Post-copy requires libvirt>=1.3.3 and QEMU>=2.5.0. + +When permitted, post-copy mode will be automatically activated if a +live-migration memory copy iteration does not make percentage increase of at +least 10% over the last iteration. + +The live-migration force complete API also uses post-copy when permitted. If +post-copy mode is not available, force complete falls back to pausing the VM +to ensure the live-migration operation will complete. + +When using post-copy mode, if the source and destination hosts loose network +connectivity, the VM being live-migrated will need to be rebooted. For more +details, please see the Administration guide. +"""), cfg.StrOpt('snapshot_image_format', choices=('raw', 'qcow2', 'vmdk', 'vdi'), help='Snapshot image format. Defaults to same as source image'), diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index 8761e69c1716..cd52bd4df412 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -94,6 +94,7 @@ VIR_MIGRATE_TUNNELLED = 4 VIR_MIGRATE_PERSIST_DEST = 8 VIR_MIGRATE_UNDEFINE_SOURCE = 16 VIR_MIGRATE_NON_SHARED_INC = 128 +VIR_MIGRATE_POSTCOPY = 32768 VIR_NODE_CPU_STATS_ALL_CPUS = -1 diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index a45ccdfb3f3d..0a3e5479a84b 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -1181,6 +1181,75 @@ class LibvirtConnTestCase(test.NoDBTestCase): msg = mock_log.warning.call_args_list[1] self.assertIn('contains the VIR_MIGRATE_TUNNELLED flag', msg[0][0]) + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + def test_live_migration_permit_postcopy_true(self, host): + self.flags(live_migration_permit_post_copy=True, group='libvirt') + self._do_test_parse_migration_flags( + lm_config=('VIR_MIGRATE_PERSIST_DEST, ' + 'VIR_MIGRATE_PEER2PEER, ' + 'VIR_MIGRATE_LIVE, ' + 'VIR_MIGRATE_TUNNELLED'), + bm_config=('VIR_MIGRATE_PERSIST_DEST, ' + 'VIR_MIGRATE_PEER2PEER, ' + 'VIR_MIGRATE_LIVE, ' + 'VIR_MIGRATE_TUNNELLED, ' + 'VIR_MIGRATE_NON_SHARED_INC'), + lm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE | + libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER | + libvirt_driver.libvirt.VIR_MIGRATE_LIVE | + libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED | + libvirt_driver.libvirt.VIR_MIGRATE_POSTCOPY), + bm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE | + libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER | + libvirt_driver.libvirt.VIR_MIGRATE_LIVE | + libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED | + libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC | + libvirt_driver.libvirt.VIR_MIGRATE_POSTCOPY)) + + @mock.patch.object(host.Host, 'has_min_version', return_value=False) + def test_live_migration_permit_postcopy_true_old_libvirt(self, host): + self.flags(live_migration_permit_post_copy=True, group='libvirt') + self._do_test_parse_migration_flags( + lm_config=('VIR_MIGRATE_PERSIST_DEST, ' + 'VIR_MIGRATE_PEER2PEER, ' + 'VIR_MIGRATE_LIVE, ' + 'VIR_MIGRATE_TUNNELLED'), + bm_config=('VIR_MIGRATE_PERSIST_DEST, ' + 'VIR_MIGRATE_PEER2PEER, ' + 'VIR_MIGRATE_LIVE, ' + 'VIR_MIGRATE_TUNNELLED, ' + 'VIR_MIGRATE_NON_SHARED_INC'), + lm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE | + libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER | + libvirt_driver.libvirt.VIR_MIGRATE_LIVE | + libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED), + bm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE | + libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER | + libvirt_driver.libvirt.VIR_MIGRATE_LIVE | + libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED | + libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC)) + + def test_live_migration_permit_postcopy_false(self): + self._do_test_parse_migration_flags( + lm_config=('VIR_MIGRATE_PERSIST_DEST, ' + 'VIR_MIGRATE_PEER2PEER, ' + 'VIR_MIGRATE_LIVE, ' + 'VIR_MIGRATE_TUNNELLED'), + bm_config=('VIR_MIGRATE_PERSIST_DEST, ' + 'VIR_MIGRATE_PEER2PEER, ' + 'VIR_MIGRATE_LIVE, ' + 'VIR_MIGRATE_TUNNELLED, ' + 'VIR_MIGRATE_NON_SHARED_INC'), + lm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE | + libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER | + libvirt_driver.libvirt.VIR_MIGRATE_LIVE | + libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED), + bm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE | + libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER | + libvirt_driver.libvirt.VIR_MIGRATE_LIVE | + libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED | + libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC)) + @mock.patch('nova.utils.get_image_from_system_metadata') @mock.patch.object(host.Host, 'has_min_version', return_value=True) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 12a1f81e7abe..7801395926ae 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -284,6 +284,12 @@ ALLOWED_QEMU_SERIAL_PORTS = QEMU_MAX_SERIAL_PORTS - 1 # realtime support MIN_LIBVIRT_REALTIME_VERSION = (1, 2, 13) +# libvirt postcopy support +MIN_LIBVIRT_POSTCOPY_VERSION = (1, 3, 3) + +# qemu postcopy support +MIN_QEMU_POSTCOPY_VERSION = (2, 5, 0) + MIN_LIBVIRT_OTHER_ARCH = {arch.S390: MIN_LIBVIRT_KVM_S390_VERSION, arch.S390X: MIN_LIBVIRT_KVM_S390_VERSION, arch.PPC: MIN_LIBVIRT_KVM_PPC64_VERSION, @@ -587,6 +593,24 @@ class LibvirtDriver(driver.ComputeDriver): return migration_flags + def _is_post_copy_available(self): + if self._host.has_min_version(lv_ver=MIN_LIBVIRT_POSTCOPY_VERSION, + hv_ver=MIN_QEMU_POSTCOPY_VERSION): + return True + return False + + def _handle_live_migration_post_copy(self, migration_flags, + config_name): + if CONF.libvirt.live_migration_permit_post_copy: + if self._is_post_copy_available(): + migration_flags |= libvirt.VIR_MIGRATE_POSTCOPY + else: + LOG.info(_LI('The live_migration_permit_post_copy is set ' + 'to True, but it is not supported.')) + elif self._is_post_copy_available(): + migration_flags &= ~libvirt.VIR_MIGRATE_POSTCOPY + return migration_flags + def _parse_migration_flags(self): def str2sum(str_val): logical_sum = 0 @@ -618,6 +642,11 @@ class LibvirtDriver(driver.ComputeDriver): block_migration_flags = self._handle_live_migration_tunnelled( block_migration_flags, block_config_name) + live_migration_flags = self._handle_live_migration_post_copy( + live_migration_flags, live_config_name) + block_migration_flags = self._handle_live_migration_post_copy( + block_migration_flags, block_config_name) + self._live_migration_flags = live_migration_flags self._block_migration_flags = block_migration_flags diff --git a/releasenotes/notes/automatic-live-migration-completion-post-copy-a7a3a986961c93d8.yaml b/releasenotes/notes/automatic-live-migration-completion-post-copy-a7a3a986961c93d8.yaml new file mode 100644 index 000000000000..09ed02c8285d --- /dev/null +++ b/releasenotes/notes/automatic-live-migration-completion-post-copy-a7a3a986961c93d8.yaml @@ -0,0 +1,6 @@ +--- +features: + - New configuration option live_migration_permit_post_copy + has been added to start live migrations in a way that allows + nova to switch an on-going live migration to post-copy mode. + Requires libvirt>=1.3.3 and QEMU>=2.5.0.