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:
parent
a8e992b105
commit
9160fe5098
@ -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="""
|
||||
|
@ -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
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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(
|
||||
|
@ -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 = {}
|
||||
|
@ -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``.
|
Loading…
Reference in New Issue
Block a user