Add new configuration option to turn auto converge on/off

Since QEMU 1.6 and Libvirt 1.2.3 it is possible to use auto
converge during live migrations. This option helps to live migrate
instances with memory-intensive workload by throttling down CPU
on guest machine.

Partially-implements: blueprint auto-live-migration-completion
Change-Id: I967bad47b67586ea2838e219eed1c6dce91ce153
This commit is contained in:
Pawel Koniszewski 2016-06-17 08:36:26 +02:00
parent 3d83c462bc
commit 0c0f60031a
5 changed files with 186 additions and 0 deletions

View File

@ -171,6 +171,24 @@ 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.
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'),

View File

@ -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_AUTO_CONVERGE = 8192
VIR_MIGRATE_POSTCOPY = 32768
VIR_NODE_CPU_STATS_ALL_CPUS = -1

View File

@ -1206,9 +1206,120 @@ class LibvirtConnTestCase(test.NoDBTestCase):
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 @@ class LibvirtConnTestCase(test.NoDBTestCase):
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)

View File

@ -272,6 +272,10 @@ MIN_LIBVIRT_PF_WITH_NO_VFS_CAP_VERSION = (1, 3, 0)
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 @@ class LibvirtDriver(driver.ComputeDriver):
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 @@ class LibvirtDriver(driver.ComputeDriver):
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

View File

@ -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.