diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py index 4df0733213a..30e2fefd24b 100644 --- a/nova/conf/libvirt.py +++ b/nova/conf/libvirt.py @@ -171,6 +171,24 @@ 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. + +Related options: + + * live_migration_permit_auto_converge +"""), + cfg.BoolOpt('live_migration_permit_auto_converge', + default=False, + help=""" +This option allows nova to start live migration with auto converge on. +Auto converge throttles down CPU if a progress of on-going live migration +is slow. Auto converge will only be used if this flag is set to True and +post copy is not permitted or post copy is unavailable due to the version +of libvirt and QEMU in use. Auto converge requires libvirt>=1.2.3 and +QEMU>=1.6.0. + +Related options: + + * live_migration_permit_post_copy """), cfg.StrOpt('snapshot_image_format', choices=('raw', 'qcow2', 'vmdk', 'vdi'), diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index cd52bd4df41..7fd7c0a1bf6 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -94,6 +94,7 @@ def _reset(): VIR_MIGRATE_PERSIST_DEST = 8 VIR_MIGRATE_UNDEFINE_SOURCE = 16 VIR_MIGRATE_NON_SHARED_INC = 128 +VIR_MIGRATE_AUTO_CONVERGE = 8192 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 0a3e5479a84..e54beacaac7 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -1206,9 +1206,120 @@ def test_live_migration_permit_postcopy_true(self, host): libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC | libvirt_driver.libvirt.VIR_MIGRATE_POSTCOPY)) + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + def test_live_migration_permit_auto_converge_true(self, host): + self.flags(live_migration_permit_auto_converge=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_AUTO_CONVERGE), + 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_AUTO_CONVERGE)) + + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + def test_live_migration_permit_auto_converge_and_post_copy_true(self, + host): + self.flags(live_migration_permit_auto_converge=True, group='libvirt') + 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') + def test_live_migration_auto_converge_and_post_copy_true_old_libvirt( + self, mock_host): + self.flags(live_migration_permit_auto_converge=True, group='libvirt') + self.flags(live_migration_permit_post_copy=True, group='libvirt') + + def fake_has_min_version(lv_ver=None, hv_ver=None, hv_type=None): + if (lv_ver == libvirt_driver.MIN_LIBVIRT_POSTCOPY_VERSION and + hv_ver == libvirt_driver.MIN_QEMU_POSTCOPY_VERSION): + return False + return True + mock_host.side_effect = fake_has_min_version + + 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_AUTO_CONVERGE), + 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_AUTO_CONVERGE)) + @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)) + + @mock.patch.object(host.Host, 'has_min_version', return_value=False) + def test_live_migration_permit_auto_converge_true_old_libvirt(self, host): + self.flags(live_migration_permit_auto_converge=True, group='libvirt') self._do_test_parse_migration_flags( lm_config=('VIR_MIGRATE_PERSIST_DEST, ' 'VIR_MIGRATE_PEER2PEER, ' @@ -1250,6 +1361,27 @@ def test_live_migration_permit_postcopy_false(self): libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED | libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC)) + def test_live_migration_permit_autoconverge_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 7801395926a..c2f80ecedf4 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -272,6 +272,10 @@ def repr_method(self): MIN_LIBVIRT_KVM_PPC64_VERSION = (1, 2, 12) MIN_QEMU_PPC64_VERSION = (2, 1, 0) +# Auto converge support +MIN_LIBVIRT_AUTO_CONVERGE_VERSION = (1, 2, 3) +MIN_QEMU_AUTO_CONVERGE = (1, 6, 0) + # Names of the types that do not get compressed during migration NO_COMPRESSION_TYPES = ('qcow2',) @@ -611,6 +615,25 @@ def _handle_live_migration_post_copy(self, migration_flags, migration_flags &= ~libvirt.VIR_MIGRATE_POSTCOPY return migration_flags + def _handle_live_migration_auto_converge(self, migration_flags, + config_name): + if self._host.has_min_version(lv_ver=MIN_LIBVIRT_AUTO_CONVERGE_VERSION, + hv_ver=MIN_QEMU_AUTO_CONVERGE): + if (self._is_post_copy_available() and + (migration_flags & libvirt.VIR_MIGRATE_POSTCOPY) != 0): + migration_flags &= ~libvirt.VIR_MIGRATE_AUTO_CONVERGE + LOG.info(_LI('The live_migration_permit_post_copy is set to ' + 'True and post copy live migration is available ' + 'so auto-converge will not be in use.')) + elif not CONF.libvirt.live_migration_permit_auto_converge: + migration_flags &= ~libvirt.VIR_MIGRATE_AUTO_CONVERGE + else: + migration_flags |= libvirt.VIR_MIGRATE_AUTO_CONVERGE + elif CONF.libvirt.live_migration_permit_auto_converge: + LOG.info(_LI('The live_migration_permit_auto_converge is set ' + 'to True, but it is not supported.')) + return migration_flags + def _parse_migration_flags(self): def str2sum(str_val): logical_sum = 0 @@ -647,6 +670,11 @@ def str2sum(str_val): block_migration_flags = self._handle_live_migration_post_copy( block_migration_flags, block_config_name) + live_migration_flags = self._handle_live_migration_auto_converge( + live_migration_flags, live_config_name) + block_migration_flags = self._handle_live_migration_auto_converge( + 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-auto-converge-3ddd3a40eaf3ef5b.yaml b/releasenotes/notes/automatic-live-migration-completion-auto-converge-3ddd3a40eaf3ef5b.yaml new file mode 100644 index 00000000000..48a4f3e360d --- /dev/null +++ b/releasenotes/notes/automatic-live-migration-completion-auto-converge-3ddd3a40eaf3ef5b.yaml @@ -0,0 +1,7 @@ +--- +features: + - New configuration option live_migration_permit_auto_converge + has been added to allow hypervisor to throttle down CPU of an + instance during live migration in case of a slow progress due + to high ratio of dirty pages. Requires libvirt>=1.2.3 and + QEMU>=1.6.0. \ No newline at end of file