libvirt: Support native TLS for migration and disks over NBD

The encryption offered by Nova (via `live_migration_tunnelled`, i.e.
"tunnelling via libvirtd") today secures only two migration streams:
guest RAM and device state; but it does _not_ encrypt the NBD (Network
Block Device) transport—which is used to migrate disks that are on
non-shared storage setup (also called: "block migration").  Further, the
"tunnelling via libvirtd" has a huge performance penalty and latency,
because it burns more CPU and memory bandwidth due to increased number
of data copies on both source and destination hosts.

To solve this existing limitation, introduce a new config option
`live_migration_with_native_tls`, which will take advantage of "native
TLS" (i.e. TLS built into QEMU, and relevant support in libvirt).  The
native TLS transport will encrypt all migration streams, *including*
disks that are not on shared storage — all of this without incurring the
limitations of the "tunnelled via libvirtd" transport.

Closes-Bug: #1798796
Blueprint: support-qemu-native-tls-for-live-migration

Change-Id: I78f5fef41b6fbf118880cc8aa4036d904626b342
Signed-off-by: Kashyap Chamarthy <kchamart@redhat.com>
This commit is contained in:
Kashyap Chamarthy 2018-12-12 16:51:52 +01:00
parent a8e992b105
commit 9160fe5098
6 changed files with 149 additions and 0 deletions

View File

@ -458,6 +458,44 @@ Determine the snapshot image format when sending to the image service.
If set, this decides what format is used when sending the snapshot to the
image service. If not set, defaults to same type as source image.
"""),
cfg.BoolOpt('live_migration_with_native_tls',
default=False,
help="""
This option will allow both migration stream (guest RAM plus device
state) *and* disk stream to be transported over native TLS, i.e. TLS
support built into QEMU.
Prerequisite: TLS environment is configured correctly on all relevant
Compute nodes. This means, Certificate Authority (CA), server, client
certificates, their corresponding keys, and their file permisssions are
in place, and are validated.
Notes:
* To have encryption for migration stream and disk stream (also called:
"block migration"), ``live_migration_with_native_tls`` is the
preferred config attribute instead of ``live_migration_tunnelled``.
* The ``live_migration_tunnelled`` will be deprecated in the long-term,
for two main reasons: (a) it incurs a huge performance penalty; and
it's not compatible with block migration.
* The ``live_migration_tunnelled`` and
``live_migration_with_native_tls`` should not be used at the same
time.
* Unlike ``live_migration_tunnelled``, the
``live_migration_with_native_tls`` *is* compatible with block
migration. That is, with this option, NBD stream, over which disks
are migrated to a target host, will be encrypted.
Related options:
``live_migration_tunnelled``: This transports migration stream (but not
disk stream) over libvirtd.
"""),
cfg.StrOpt('disk_prefix',
help="""

View File

@ -98,6 +98,7 @@ VIR_MIGRATE_UNDEFINE_SOURCE = 16
VIR_MIGRATE_NON_SHARED_INC = 128
VIR_MIGRATE_AUTO_CONVERGE = 8192
VIR_MIGRATE_POSTCOPY = 32768
VIR_MIGRATE_TLS = 65536
VIR_NODE_CPU_STATS_ALL_CPUS = -1

View File

@ -1284,6 +1284,22 @@ class LibvirtConnTestCase(test.NoDBTestCase,
libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC |
libvirt_driver.libvirt.VIR_MIGRATE_TUNNELLED))
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
def test_live_migration_with_native_tls(self, host):
self.flags(live_migration_with_native_tls=True, group='libvirt')
self._do_test_parse_migration_flags(
lm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_LIVE |
libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER |
libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE |
libvirt_driver.libvirt.VIR_MIGRATE_PERSIST_DEST |
libvirt_driver.libvirt.VIR_MIGRATE_TLS),
bm_expected=(libvirt_driver.libvirt.VIR_MIGRATE_LIVE |
libvirt_driver.libvirt.VIR_MIGRATE_PEER2PEER |
libvirt_driver.libvirt.VIR_MIGRATE_UNDEFINE_SOURCE |
libvirt_driver.libvirt.VIR_MIGRATE_PERSIST_DEST |
libvirt_driver.libvirt.VIR_MIGRATE_NON_SHARED_INC |
libvirt_driver.libvirt.VIR_MIGRATE_TLS))
@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')
@ -10439,6 +10455,52 @@ class LibvirtConnTestCase(test.NoDBTestCase,
drvr._live_migration_uri(target_connection),
params=params, flags=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',
return_value='')
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='')
def test_block_live_migration_native_tls_migrateToURI3(
self, mock_old_xml, mock_new_xml,
mock_migrateToURI3, mock_min_version):
self.flags(live_migration_with_native_tls=True, group='libvirt')
target_connection = None
disk_paths = ['vda', 'vdb']
params = {
'bandwidth': CONF.libvirt.live_migration_bandwidth,
'migrate_disks': disk_paths
}
# Start test
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=target_connection,
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,
target_connection, True, migrate_data,
guest, disk_paths)
expected_flags = (fakelibvirt.VIR_MIGRATE_UNDEFINE_SOURCE |
fakelibvirt.VIR_MIGRATE_PERSIST_DEST |
fakelibvirt.VIR_MIGRATE_PEER2PEER |
fakelibvirt.VIR_MIGRATE_NON_SHARED_INC |
fakelibvirt.VIR_MIGRATE_TLS |
fakelibvirt.VIR_MIGRATE_LIVE)
mock_migrateToURI3.assert_called_once_with(
drvr._live_migration_uri(target_connection),
params=params, flags=expected_flags)
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
@mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc',

View File

@ -288,6 +288,9 @@ MIN_QEMU_FILE_BACKED_VERSION = (2, 6, 0)
MIN_LIBVIRT_FILE_BACKED_DISCARD_VERSION = (4, 4, 0)
MIN_QEMU_FILE_BACKED_DISCARD_VERSION = (2, 10, 0)
MIN_LIBVIRT_NATIVE_TLS_VERSION = (4, 4, 0)
MIN_QEMU_NATIVE_TLS_VERSION = (2, 11, 0)
VGPU_RESOURCE_SEMAPHORE = "vgpu_resources"
@ -543,6 +546,20 @@ class LibvirtDriver(driver.ComputeDriver):
'libvirt_ver': libvirt_utils.version_to_string(
MIN_LIBVIRT_OTHER_ARCH.get(kvm_arch))})
# Allowing both "tunnelling via libvirtd" (which will be
# deprecated once the MIN_{LIBVIRT,QEMU}_VERSION is sufficiently
# new enough) and "native TLS" options at the same time is
# nonsensical.
if (CONF.libvirt.live_migration_tunnelled and
CONF.libvirt.live_migration_with_native_tls):
msg = _("Setting both 'live_migration_tunnelled' and"
"'live_migration_with_native_tls' at the same"
"time is invalid. If you have the relevant"
"libvirt and QEMU versions, and TLS configured"
"in your environment, pick"
"'live_migration_with_native_tls'.")
raise exception.Invalid(msg)
# TODO(sbauza): Remove this code once mediated devices are persisted
# across reboots.
if self._host.has_min_version(MIN_LIBVIRT_MDEV_SUPPORT):
@ -643,6 +660,16 @@ class LibvirtDriver(driver.ComputeDriver):
migration_flags |= libvirt.VIR_MIGRATE_TUNNELLED
return migration_flags
def _is_native_tls_available(self):
return self._host.has_min_version(MIN_LIBVIRT_NATIVE_TLS_VERSION,
MIN_QEMU_NATIVE_TLS_VERSION)
def _handle_native_tls(self, migration_flags):
if (CONF.libvirt.live_migration_with_native_tls and
self._is_native_tls_available()):
migration_flags |= libvirt.VIR_MIGRATE_TLS
return migration_flags
def _is_post_copy_available(self):
return self._host.has_min_version(lv_ver=MIN_LIBVIRT_POSTCOPY_VERSION)
@ -682,6 +709,11 @@ class LibvirtDriver(driver.ComputeDriver):
block_migration_flags = self._handle_live_migration_tunnelled(
block_migration_flags)
live_migration_flags = self._handle_native_tls(
live_migration_flags)
block_migration_flags = self._handle_native_tls(
block_migration_flags)
live_migration_flags = self._handle_live_migration_post_copy(
live_migration_flags)
block_migration_flags = self._handle_live_migration_post_copy(

View File

@ -636,6 +636,7 @@ class Guest(object):
VIR_MIGRATE_UNSAFE Force migration even if it is considered
unsafe.
VIR_MIGRATE_OFFLINE Migrate offline
VIR_MIGRATE_TLS Use QEMU-native TLS
:param bandwidth: The maximum bandwidth in MiB/s
"""
params = {}

View File

@ -0,0 +1,15 @@
---
features:
- |
The libvirt driver now supports "QEMU-native TLS" transport for live
migration. This will provide encryption for all migration streams,
namely: guest RAM, device state and disks on a non-shared setup that are
transported over NBD (Network Block Device), also called as "block
migration".
This can be configured via a new configuration attribute
``[libvirt]/live_migration_with_native_tls``. Refer to its
documentation in ``nova.conf`` for usage details. Note that this is
the preferred the way to secure all migration streams in an
OpenStack network, instead of
``[libvirt]/live_migration_tunnelled``.