libvirt: QEMU native LUKS decryption for encrypted volumes
QEMU 2.6 and Libvirt 2.2.0 allow LUKS encrypted RAW files, block devices and network devices (such as rbd) to be decrypted natively by QEMU. This change enables the use of this feature within Nova when the appropriate versions of QEMU and Libvirt are installed and the encryption provider for the volume is of type 'luks'. When these conditions are met a Libvirt secret is created when connecting encrypted volumes to a compute host to hold the LUKS passphrase used to unlock the volume. The presence of this Libvirt secret is then used by the volume driver to generate the required encryption XML for the disk. QEMU is then able to natively read from and write to the encrypted disk, removing the need for the os-brick supplied dm-crypt style encryptors, previously used to handle encrypted volumes. When disconnecting a volume the presence of a Libvirt secret will result in no attempt being made to detach an os-brick provided encryptor from the volume. This will only occur when no secret for the volume is found on the compute host. This will allow encrypted volumes attached prior to this change to still be detached correctly. Attempts to swap between volumes while using native QEMU decryption will be blocked in the same manner as they are when using volumes that do not provide a local block device. Both use cases still require additional implementation work in Libvirt before being allowed within Nova. LibvirtLiveMigrateData and LibvirtLiveMigrateBDMInfo are both extended to support the following matrix of live migration (LM) scenarios: - Pike using os-brick encryptors to Queens using os-brick encryptors - Queens using os-brick encryptors to Queens using os-brick encryptors - Queens using os-brick encryptors to Queens using native QEMU decrypt - Queens using native QEMU decrypt to Queens using native QEMU decrypt A new 'src_supports_native_luks' attribute has been added to LibvirtLiveMigrateData in Queens to indicate that the source host is capable of configuring QEMU LUKS decryption for a volume during LM. The presence of this attribute in migrate_data during pre_live_migration on the destination is then used to decide how encrypted volumes are connected on that host ahead of LM starting. When missing or False the os-brick encryptors will be attached, when present and True the native QEMU decryption approach will be taken only if the destination host also supports native LUKS decryption by QEMU. The UUID of this secret is then stored in the individual LibvirtLiveMigrateBDMInfo object associated with the volume and passed back to the source host to be added to the volume configuration prior to the start of the live migration. Implements: blueprint libvirt-qemu-native-luks Change-Id: Ibfa64f18bbd2fb70db7791330ed1a64fe61c1355
This commit is contained in:
parent
7e5d93edc5
commit
f8e24c33f8
|
@ -68,7 +68,10 @@ class LiveMigrateData(obj_base.NovaObject):
|
|||
|
||||
@obj_base.NovaObjectRegistry.register
|
||||
class LibvirtLiveMigrateBDMInfo(obj_base.NovaObject):
|
||||
VERSION = '1.0'
|
||||
# VERSION 1.0 : Initial version
|
||||
# VERSION 1.1 : Added encryption_secret_uuid for tracking volume secret
|
||||
# uuid created on dest during migration with encrypted vols.
|
||||
VERSION = '1.1'
|
||||
|
||||
fields = {
|
||||
# FIXME(danms): some of these can be enums?
|
||||
|
@ -79,8 +82,16 @@ class LibvirtLiveMigrateBDMInfo(obj_base.NovaObject):
|
|||
'format': fields.StringField(nullable=True),
|
||||
'boot_index': fields.IntegerField(nullable=True),
|
||||
'connection_info_json': fields.StringField(),
|
||||
'encryption_secret_uuid': fields.UUIDField(nullable=True),
|
||||
}
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
super(LibvirtLiveMigrateBDMInfo, self).obj_make_compatible(
|
||||
primitive, target_version)
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version < (1, 1) and 'encryption_secret_uuid' in primitive:
|
||||
del primitive['encryption_secret_uuid']
|
||||
|
||||
# NOTE(danms): We don't have a connection_info object right
|
||||
# now, and instead mostly store/pass it as JSON that we're
|
||||
# careful with. When we get a connection_info object in the
|
||||
|
@ -115,7 +126,8 @@ class LibvirtLiveMigrateData(LiveMigrateData):
|
|||
# serial console.
|
||||
# Version 1.3: Added 'supported_perf_events'
|
||||
# Version 1.4: Added old_vol_attachment_ids
|
||||
VERSION = '1.4'
|
||||
# Version 1.5: Added src_supports_native_luks
|
||||
VERSION = '1.5'
|
||||
|
||||
fields = {
|
||||
'filename': fields.StringField(),
|
||||
|
@ -134,12 +146,16 @@ class LibvirtLiveMigrateData(LiveMigrateData):
|
|||
'bdms': fields.ListOfObjectsField('LibvirtLiveMigrateBDMInfo'),
|
||||
'target_connect_addr': fields.StringField(nullable=True),
|
||||
'supported_perf_events': fields.ListOfStringsField(),
|
||||
'src_supports_native_luks': fields.BooleanField(),
|
||||
}
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
super(LibvirtLiveMigrateData, self).obj_make_compatible(
|
||||
primitive, target_version)
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version < (1, 5):
|
||||
if 'src_supports_native_luks' in primitive:
|
||||
del primitive['src_supports_native_luks']
|
||||
if target_version < (1, 4):
|
||||
if 'old_vol_attachment_ids' in primitive:
|
||||
del primitive['old_vol_attachment_ids']
|
||||
|
|
|
@ -225,11 +225,31 @@ class _TestLibvirtLiveMigrateData(object):
|
|||
|
||||
def test_obj_make_compatible(self):
|
||||
obj = migrate_data.LibvirtLiveMigrateData(
|
||||
old_vol_attachment_ids={uuids.volume: uuids.attachment})
|
||||
src_supports_native_luks=True,
|
||||
old_vol_attachment_ids={uuids.volume: uuids.attachment},
|
||||
supported_perf_events=[],
|
||||
serial_listen_addr='127.0.0.1',
|
||||
target_connect_addr='127.0.0.1')
|
||||
primitive = obj.obj_to_primitive(target_version='1.0')
|
||||
self.assertNotIn('target_connect_addr', primitive)
|
||||
self.assertNotIn('serial_listen_addr=', primitive)
|
||||
self.assertNotIn('supported_perf_events', primitive)
|
||||
self.assertNotIn('old_vol_attachment_ids', primitive)
|
||||
self.assertNotIn('src_supports_native_luks', primitive)
|
||||
primitive = obj.obj_to_primitive(target_version='1.1')
|
||||
self.assertNotIn('serial_listen_addr=', primitive)
|
||||
primitive = obj.obj_to_primitive(target_version='1.2')
|
||||
self.assertNotIn('supported_perf_events', primitive)
|
||||
primitive = obj.obj_to_primitive(target_version='1.3')
|
||||
self.assertNotIn('old_vol_attachment_ids', primitive)
|
||||
primitive = obj.obj_to_primitive(target_version='1.4')
|
||||
self.assertNotIn('src_supports_native_luks', primitive)
|
||||
|
||||
def test_bdm_obj_make_compatible(self):
|
||||
obj = migrate_data.LibvirtLiveMigrateBDMInfo(
|
||||
encryption_secret_uuid=uuids.encryption_secret_uuid)
|
||||
primitive = obj.obj_to_primitive(target_version='1.0')
|
||||
self.assertNotIn('encryption_secret_uuid', primitive)
|
||||
|
||||
|
||||
class TestLibvirtLiveMigrateData(test_objects._LocalTest,
|
||||
|
|
|
@ -1118,8 +1118,8 @@ object_data = {
|
|||
'InstanceNUMATopology': '1.3-ec0030cb0402a49c96da7051c037082a',
|
||||
'InstancePCIRequest': '1.2-6344dd8bd1bf873e7325c07afe47f774',
|
||||
'InstancePCIRequests': '1.1-65e38083177726d806684cb1cc0136d2',
|
||||
'LibvirtLiveMigrateBDMInfo': '1.0-252aabb723ca79d5469fa56f64b57811',
|
||||
'LibvirtLiveMigrateData': '1.4-ae5f344e7f78d3b45c259a0f80ea69f5',
|
||||
'LibvirtLiveMigrateBDMInfo': '1.1-5f4a68873560b6f834b74e7861d71aaf',
|
||||
'LibvirtLiveMigrateData': '1.5-26f8beff5fe9489efe3dfd3ab7a9eaec',
|
||||
'KeyPair': '1.4-1244e8d1b103cc69d038ed78ab3a8cc6',
|
||||
'KeyPairList': '1.3-94aad3ac5c938eef4b5e83da0212f506',
|
||||
'MemoryDiagnostics': '1.0-2c995ae0f2223bb0f8e523c5cc0b83da',
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
from collections import deque
|
||||
from collections import OrderedDict
|
||||
import contextlib
|
||||
|
@ -28,6 +29,7 @@ import signal
|
|||
import threading
|
||||
import time
|
||||
|
||||
from castellan import key_manager
|
||||
import ddt
|
||||
import eventlet
|
||||
from eventlet import greenthread
|
||||
|
@ -3471,7 +3473,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
|
||||
conn_info = {'driver_volume_type': 'fake'}
|
||||
conn_info = {'driver_volume_type': 'fake', 'data': {}}
|
||||
bdms = block_device_obj.block_device_make_list_from_dicts(
|
||||
self.context, [
|
||||
fake_block_device.FakeDbBlockDeviceDict(
|
||||
|
@ -3514,7 +3516,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
|
||||
conn_info = {'driver_volume_type': 'fake'}
|
||||
conn_info = {'driver_volume_type': 'fake', 'data': {}}
|
||||
bdms = block_device_obj.block_device_make_list_from_dicts(
|
||||
self.context, [
|
||||
fake_block_device.FakeDbBlockDeviceDict(
|
||||
|
@ -3631,7 +3633,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
"properties": {"hw_scsi_model": "virtio-scsi",
|
||||
"hw_disk_bus": "scsi"}})
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
conn_info = {'driver_volume_type': 'fake'}
|
||||
conn_info = {'driver_volume_type': 'fake', 'data': {}}
|
||||
bdms = block_device_obj.block_device_make_list_from_dicts(
|
||||
self.context, [
|
||||
fake_block_device.FakeDbBlockDeviceDict(
|
||||
|
@ -6595,6 +6597,113 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
_set_cache_mode.assert_called_once_with(config)
|
||||
self.assertEqual(config_guest_disk.to_xml(), config.to_xml())
|
||||
|
||||
@mock.patch.object(key_manager, 'API')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_use_native_luks')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryptor')
|
||||
@mock.patch('nova.virt.libvirt.host.Host')
|
||||
@mock.patch('os_brick.encryptors.luks.is_luks')
|
||||
def test_connect_volume_native_luks(self, mock_is_luks, mock_host,
|
||||
mock_get_volume_encryptor, mock_use_native_luks,
|
||||
mock_get_volume_encryption, mock_get_key_mgr):
|
||||
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
connection_info = {'driver_volume_type': 'fake',
|
||||
'data': {'device_path': '/fake',
|
||||
'access_mode': 'rw',
|
||||
'volume_id': uuids.volume_id}}
|
||||
encryption = {'provider': encryptors.LUKS,
|
||||
'encryption_key_id': uuids.encryption_key_id}
|
||||
instance = mock.sentinel.instance
|
||||
|
||||
# Mock out the encryptors
|
||||
mock_encryptor = mock.Mock()
|
||||
mock_get_volume_encryptor.return_value = mock_encryptor
|
||||
mock_is_luks.return_value = True
|
||||
|
||||
# Mock out the key manager
|
||||
key = u'3734363537333734'
|
||||
key_encoded = binascii.unhexlify(key)
|
||||
mock_key = mock.Mock()
|
||||
mock_key_mgr = mock.Mock()
|
||||
mock_get_key_mgr.return_value = mock_key_mgr
|
||||
mock_key_mgr.get.return_value = mock_key
|
||||
mock_key.get_encoded.return_value = key_encoded
|
||||
|
||||
# assert that the secret is created for the encrypted volume during
|
||||
# _connect_volume when use_native_luks is True
|
||||
mock_get_volume_encryption.return_value = encryption
|
||||
mock_use_native_luks.return_value = True
|
||||
|
||||
drvr._connect_volume(self.context, connection_info, instance,
|
||||
encryption=encryption)
|
||||
drvr._host.create_secret.assert_called_once_with('volume',
|
||||
uuids.volume_id, password=key)
|
||||
mock_encryptor.attach_volume.assert_not_called()
|
||||
|
||||
# assert that the encryptor is used if use_native_luks is False
|
||||
drvr._host.create_secret.reset_mock()
|
||||
mock_get_volume_encryption.reset_mock()
|
||||
mock_use_native_luks.return_value = False
|
||||
|
||||
drvr._connect_volume(self.context, connection_info, instance,
|
||||
encryption=encryption)
|
||||
drvr._host.create_secret.assert_not_called()
|
||||
mock_encryptor.attach_volume.assert_called_once_with(self.context,
|
||||
**encryption)
|
||||
|
||||
# assert that we format the volume if is_luks is False
|
||||
mock_use_native_luks.return_value = True
|
||||
mock_is_luks.return_value = False
|
||||
|
||||
drvr._connect_volume(self.context, connection_info, instance,
|
||||
encryption=encryption)
|
||||
mock_encryptor._format_volume.assert_called_once_with(key,
|
||||
**encryption)
|
||||
|
||||
# assert that os-brick is used when allow_native_luks is False
|
||||
mock_encryptor.attach_volume.reset_mock()
|
||||
mock_is_luks.return_value = True
|
||||
|
||||
drvr._connect_volume(self.context, connection_info, instance,
|
||||
encryption=encryption, allow_native_luks=False)
|
||||
mock_encryptor.attach_volume.assert_called_once_with(self.context,
|
||||
**encryption)
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryptor')
|
||||
def test_disconnect_volume_native_luks(self, mock_get_volume_encryptor):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
drvr._host = mock.Mock()
|
||||
drvr._host.find_secret.return_value = mock.Mock()
|
||||
connection_info = {'driver_volume_type': 'fake',
|
||||
'data': {'device_path': '/fake',
|
||||
'access_mode': 'rw',
|
||||
'volume_id': uuids.volume_id}}
|
||||
encryption = {'provider': encryptors.LUKS,
|
||||
'encryption_key_id': uuids.encryption_key_id}
|
||||
instance = mock.sentinel.instance
|
||||
|
||||
# Mock out the encryptors
|
||||
mock_encryptor = mock.Mock()
|
||||
mock_get_volume_encryptor.return_value = mock_encryptor
|
||||
|
||||
# assert that a secret is deleted if found
|
||||
drvr._disconnect_volume(self.context, connection_info, instance)
|
||||
drvr._host.delete_secret.assert_called_once_with('volume',
|
||||
uuids.volume_id)
|
||||
mock_encryptor.detach_volume.assert_not_called()
|
||||
|
||||
# assert that the encryptor is used if no secret is found
|
||||
drvr._host.find_secret.reset_mock()
|
||||
drvr._host.delete_secret.reset_mock()
|
||||
drvr._host.find_secret.return_value = None
|
||||
|
||||
drvr._disconnect_volume(self.context, connection_info, instance,
|
||||
encryption=encryption)
|
||||
drvr._host.delete_secret.assert_not_called()
|
||||
mock_encryptor.detach_volume.called_once_with(self.context,
|
||||
**encryption)
|
||||
|
||||
def test_attach_invalid_volume_type(self):
|
||||
self.create_fake_libvirt_mock()
|
||||
libvirt_driver.LibvirtDriver._conn.lookupByUUIDString \
|
||||
|
@ -6930,7 +7039,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
connection_info = {'data': {}}
|
||||
|
||||
drvr._attach_encryptor(self.context, connection_info, None)
|
||||
drvr._attach_encryptor(self.context, connection_info, None, False)
|
||||
|
||||
mock_get_metadata.assert_not_called()
|
||||
mock_get_encryptor.assert_not_called()
|
||||
|
@ -6948,7 +7057,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
connection_info = {'data': {'volume_id': uuids.volume_id}}
|
||||
mock_get_metadata.return_value = encryption
|
||||
|
||||
drvr._attach_encryptor(self.context, connection_info, None)
|
||||
drvr._attach_encryptor(self.context, connection_info, None, False)
|
||||
|
||||
mock_get_metadata.assert_called_once_with(self.context,
|
||||
drvr._volume_api, uuids.volume_id, connection_info)
|
||||
|
@ -6966,8 +7075,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
encryption = {}
|
||||
connection_info = {'data': {'volume_id': uuids.volume_id}}
|
||||
|
||||
drvr._attach_encryptor(self.context, connection_info,
|
||||
encryption=encryption)
|
||||
drvr._attach_encryptor(self.context, connection_info, encryption,
|
||||
False)
|
||||
|
||||
mock_get_metadata.assert_not_called()
|
||||
mock_get_encryptor.assert_not_called()
|
||||
|
@ -6986,7 +7095,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
mock_get_metadata.return_value = encryption
|
||||
connection_info = {'data': {'volume_id': uuids.volume_id}}
|
||||
|
||||
drvr._attach_encryptor(self.context, connection_info, None)
|
||||
drvr._attach_encryptor(self.context, connection_info, None, False)
|
||||
|
||||
mock_get_metadata.assert_called_once_with(self.context,
|
||||
drvr._volume_api, uuids.volume_id, connection_info)
|
||||
|
@ -7010,7 +7119,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
connection_info = {'data': {'volume_id': uuids.volume_id}}
|
||||
|
||||
drvr._attach_encryptor(self.context, connection_info,
|
||||
encryption=encryption)
|
||||
encryption, False)
|
||||
|
||||
mock_get_metadata.assert_not_called()
|
||||
mock_get_encryptor.assert_called_once_with(connection_info,
|
||||
|
@ -7111,6 +7220,46 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
encryption)
|
||||
mock_encryptor.detach_volume.assert_called_once_with(**encryption)
|
||||
|
||||
@mock.patch.object(host.Host, "has_min_version")
|
||||
def test_use_native_luks(self, mock_has_min_version):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||
# True only when the required QEMU and Libvirt versions are available
|
||||
# on the host and a valid LUKS provider is present within the
|
||||
# encryption metadata dict.
|
||||
mock_has_min_version.return_value = True
|
||||
self.assertFalse(drvr._use_native_luks({}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': 'nova.volume.encryptors.cryptsetup.CryptSetupEncryptor'
|
||||
}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': 'CryptSetupEncryptor'}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': encryptors.PLAIN}))
|
||||
self.assertTrue(drvr._use_native_luks({
|
||||
'provider': 'nova.volume.encryptors.luks.LuksEncryptor'}))
|
||||
self.assertTrue(drvr._use_native_luks({
|
||||
'provider': 'LuksEncryptor'}))
|
||||
self.assertTrue(drvr._use_native_luks({
|
||||
'provider': encryptors.LUKS}))
|
||||
|
||||
# Always False when the required QEMU and Libvirt versions are not
|
||||
# available on the host.
|
||||
mock_has_min_version.return_value = False
|
||||
self.assertFalse(drvr._use_native_luks({}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': 'nova.volume.encryptors.cryptsetup.CryptSetupEncryptor'
|
||||
}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': 'CryptSetupEncryptor'}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': encryptors.PLAIN}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': 'nova.volume.encryptors.luks.LuksEncryptor'}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': 'LuksEncryptor'}))
|
||||
self.assertFalse(drvr._use_native_luks({
|
||||
'provider': encryptors.LUKS}))
|
||||
|
||||
def test_multi_nic(self):
|
||||
network_info = _fake_network_info(self, 2)
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
|
@ -10306,8 +10455,23 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
target_ret = self._generate_target_ret('127.0.0.2')
|
||||
self._test_pre_live_migration_works_correctly_mocked(target_ret)
|
||||
|
||||
def test_pre_live_migration_only_dest_supports_native_luks(self):
|
||||
# Assert that allow_native_luks is False when src_supports_native_luks
|
||||
# is missing from migrate data during a P to Q LM.
|
||||
self._test_pre_live_migration_works_correctly_mocked(
|
||||
src_supports_native_luks=None, dest_supports_native_luks=True,
|
||||
allow_native_luks=False)
|
||||
|
||||
def test_pre_live_migration_only_src_supports_native_luks(self):
|
||||
# Assert that allow_native_luks is False when dest_supports_native_luks
|
||||
# is False due to unmet QEMU and Libvirt deps on the dest compute.
|
||||
self._test_pre_live_migration_works_correctly_mocked(
|
||||
src_supports_native_luks=True, dest_supports_native_luks=False,
|
||||
allow_native_luks=False)
|
||||
|
||||
def _test_pre_live_migration_works_correctly_mocked(self,
|
||||
target_ret=None):
|
||||
target_ret=None, src_supports_native_luks=True,
|
||||
dest_supports_native_luks=True, allow_native_luks=True):
|
||||
# Creating testdata
|
||||
vol = {'block_device_mapping': [
|
||||
{'connection_info': {'serial': '12345', u'data':
|
||||
|
@ -10329,6 +10493,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
return
|
||||
|
||||
self.stubs.Set(drvr, '_create_images_and_backing', fake_none)
|
||||
self.stubs.Set(drvr, '_is_native_luks_available',
|
||||
lambda: dest_supports_native_luks)
|
||||
|
||||
instance = objects.Instance(**self.test_instance)
|
||||
c = context.get_admin_context()
|
||||
|
@ -10340,7 +10506,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
).AndReturn(vol['block_device_mapping'])
|
||||
self.mox.StubOutWithMock(drvr, "_connect_volume")
|
||||
for v in vol['block_device_mapping']:
|
||||
drvr._connect_volume(c, v['connection_info'], instance)
|
||||
drvr._connect_volume(c, v['connection_info'], instance,
|
||||
allow_native_luks=allow_native_luks)
|
||||
self.mox.StubOutWithMock(drvr, 'plug_vifs')
|
||||
drvr.plug_vifs(mox.IsA(instance), nw_info)
|
||||
|
||||
|
@ -10354,6 +10521,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
graphics_listen_addr_spice='127.0.0.1',
|
||||
serial_listen_addr='127.0.0.1',
|
||||
)
|
||||
|
||||
if src_supports_native_luks:
|
||||
migrate_data.src_supports_native_luks = True
|
||||
|
||||
result = drvr.pre_live_migration(
|
||||
c, instance, vol, nw_info, None,
|
||||
migrate_data=migrate_data)
|
||||
|
@ -10462,6 +10633,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
return
|
||||
|
||||
self.stubs.Set(drvr, '_create_images_and_backing', fake_none)
|
||||
self.stubs.Set(drvr, '_is_native_luks_available', lambda: True)
|
||||
|
||||
class FakeNetworkInfo(object):
|
||||
def fixed_ips(self):
|
||||
|
@ -10472,7 +10644,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
# Creating mocks
|
||||
self.mox.StubOutWithMock(drvr, "_connect_volume")
|
||||
for v in vol['block_device_mapping']:
|
||||
drvr._connect_volume(c, v['connection_info'], inst_ref)
|
||||
drvr._connect_volume(c, v['connection_info'], inst_ref,
|
||||
allow_native_luks=True)
|
||||
self.mox.StubOutWithMock(drvr, 'plug_vifs')
|
||||
drvr.plug_vifs(mox.IsA(inst_ref), nw_info)
|
||||
self.mox.ReplayAll()
|
||||
|
@ -10486,6 +10659,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
disk_available_mb=123,
|
||||
image_type='qcow2',
|
||||
filename='foo',
|
||||
src_supports_native_luks=True,
|
||||
)
|
||||
ret = drvr.pre_live_migration(c, inst_ref, vol, nw_info, None,
|
||||
migrate_data)
|
||||
|
@ -15389,6 +15563,16 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
self.assertTrue(instance.cleaned)
|
||||
save.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_use_native_luks')
|
||||
def test_swap_volume_native_luks_blocked(self, mock_use_native_luks,
|
||||
mock_get_encryption):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
|
||||
mock_get_encryption.return_value = {'provider': 'luks'}
|
||||
mock_use_native_luks.return_value = True
|
||||
self.assertRaises(NotImplementedError, drvr.swap_volume, self.context,
|
||||
{}, {}, None, None, None)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.guest.BlockDevice.is_job_complete',
|
||||
return_value=True)
|
||||
def _test_swap_volume(self, mock_is_job_complete, source_type,
|
||||
|
@ -15528,6 +15712,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
old_connection_info,
|
||||
instance)
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
|
||||
@mock.patch('nova.virt.libvirt.guest.BlockDevice.rebase')
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._connect_volume')
|
||||
|
@ -15537,7 +15722,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
@mock.patch('nova.virt.libvirt.host.Host.write_instance_config')
|
||||
def test_swap_volume_disconnect_new_volume_on_rebase_error(self,
|
||||
write_config, get_guest, get_disk, get_volume_config,
|
||||
connect_volume, disconnect_volume, rebase):
|
||||
connect_volume, disconnect_volume, rebase, get_volume_encryption):
|
||||
"""Assert that disconnect_volume is called for the new volume if an
|
||||
error is encountered while rebasing
|
||||
"""
|
||||
|
@ -15545,6 +15730,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
instance = objects.Instance(**self.test_instance)
|
||||
guest = libvirt_guest.Guest(mock.MagicMock())
|
||||
get_guest.return_value = guest
|
||||
get_volume_encryption.return_value = {}
|
||||
exc = fakelibvirt.make_libvirtError(fakelibvirt.libvirtError,
|
||||
'internal error', error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR)
|
||||
rebase.side_effect = exc
|
||||
|
@ -15558,6 +15744,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
disconnect_volume.assert_called_once_with(self.context,
|
||||
mock.sentinel.new_connection_info, instance)
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
|
||||
@mock.patch('nova.virt.libvirt.guest.BlockDevice.is_job_complete')
|
||||
@mock.patch('nova.virt.libvirt.guest.BlockDevice.abort_job')
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
|
||||
|
@ -15568,7 +15755,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
@mock.patch('nova.virt.libvirt.host.Host.write_instance_config')
|
||||
def test_swap_volume_disconnect_new_volume_on_pivot_error(self,
|
||||
write_config, get_guest, get_disk, get_volume_config,
|
||||
connect_volume, disconnect_volume, abort_job, is_job_complete):
|
||||
connect_volume, disconnect_volume, abort_job, is_job_complete,
|
||||
get_volume_encryption):
|
||||
"""Assert that disconnect_volume is called for the new volume if an
|
||||
error is encountered while pivoting to the new volume
|
||||
"""
|
||||
|
@ -15576,6 +15764,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
instance = objects.Instance(**self.test_instance)
|
||||
guest = libvirt_guest.Guest(mock.MagicMock())
|
||||
get_guest.return_value = guest
|
||||
get_volume_encryption.return_value = {}
|
||||
exc = fakelibvirt.make_libvirtError(fakelibvirt.libvirtError,
|
||||
'internal error', error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR)
|
||||
is_job_complete.return_value = True
|
||||
|
@ -15892,7 +16081,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
|||
instance_ref = objects.Instance(**ct_instance)
|
||||
|
||||
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
|
||||
conn_info = {'driver_volume_type': 'fake'}
|
||||
conn_info = {'driver_volume_type': 'fake', 'data': {}}
|
||||
bdm = objects.BlockDeviceMapping(
|
||||
self.context,
|
||||
**fake_block_device.FakeDbBlockDeviceDict(
|
||||
|
|
|
@ -24,6 +24,7 @@ from nova import objects
|
|||
from nova import test
|
||||
from nova.tests.unit import matchers
|
||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||
from nova.tests import uuidsentinel as uuids
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
from nova.virt.libvirt import guest as libvirt_guest
|
||||
from nova.virt.libvirt import host
|
||||
|
@ -314,6 +315,163 @@ class UtilityMigrationTestCase(test.NoDBTestCase):
|
|||
'sdc')
|
||||
self.assertThat(res, matchers.XMLMatches(new_xml))
|
||||
|
||||
def test_update_volume_xml_add_encryption(self):
|
||||
connection_info = {
|
||||
'driver_volume_type': 'rbd',
|
||||
'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192',
|
||||
'data': {
|
||||
'access_mode': 'rw',
|
||||
'secret_type': 'ceph',
|
||||
'name': 'cinder-volumes/volume-d299a078',
|
||||
'encrypted': False,
|
||||
'discard': True,
|
||||
'cluster_name': 'ceph',
|
||||
'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9',
|
||||
'qos_specs': None,
|
||||
'auth_enabled': True,
|
||||
'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192',
|
||||
'hosts': ['172.16.128.101', '172.16.128.121'],
|
||||
'auth_username': 'cinder',
|
||||
'ports': ['6789', '6789', '6789']}}
|
||||
bdm = objects.LibvirtLiveMigrateBDMInfo(
|
||||
serial='d299a078-f0db-4993-bf03-f10fe44fd192',
|
||||
bus='scsi', type='disk', dev='sdb',
|
||||
connection_info=connection_info,
|
||||
encryption_secret_uuid=uuids.encryption_secret_uuid)
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
target_connect_addr=None,
|
||||
bdms=[bdm],
|
||||
block_migration=False)
|
||||
xml = """<domain>
|
||||
<devices>
|
||||
<disk type='network' device='disk'>
|
||||
<driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
|
||||
<auth username='cinder'>
|
||||
<secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
|
||||
</auth>
|
||||
<source protocol='rbd' name='cinder-volumes/volume-d299a078'>
|
||||
<host name='172.16.128.101' port='6789'/>
|
||||
<host name='172.16.128.121' port='6789'/>
|
||||
</source>
|
||||
<backingStore/>
|
||||
<target dev='sdb' bus='scsi'/>
|
||||
<serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
|
||||
<alias name='scsi0-0-0-1'/>
|
||||
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
|
||||
</disk>
|
||||
</devices>
|
||||
</domain>"""
|
||||
new_xml = """<domain>
|
||||
<devices>
|
||||
<disk type='network' device='disk'>
|
||||
<driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
|
||||
<auth username='cinder'>
|
||||
<secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
|
||||
</auth>
|
||||
<source protocol='rbd' name='cinder-volumes/volume-d299a078'>
|
||||
<host name='172.16.128.101' port='6789'/>
|
||||
<host name='172.16.128.121' port='6789'/>
|
||||
</source>
|
||||
<backingStore/>
|
||||
<target dev='sdb' bus='scsi'/>
|
||||
<serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
|
||||
<alias name='scsi0-0-0-1'/>
|
||||
<encryption format='luks'>
|
||||
<secret type='passphrase' uuid='%(encryption_secret_uuid)s'/>
|
||||
</encryption>
|
||||
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
|
||||
</disk>
|
||||
</devices>
|
||||
</domain>""" % {'encryption_secret_uuid': uuids.encryption_secret_uuid}
|
||||
conf = vconfig.LibvirtConfigGuestDisk()
|
||||
conf.source_device = bdm.type
|
||||
conf.driver_name = "qemu"
|
||||
conf.driver_format = "raw"
|
||||
conf.driver_cache = "writeback"
|
||||
conf.target_dev = bdm.dev
|
||||
conf.target_bus = bdm.bus
|
||||
conf.serial = bdm.connection_info.get('serial')
|
||||
conf.source_type = "network"
|
||||
conf.driver_discard = 'unmap'
|
||||
conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive()
|
||||
conf.device_addr.controller = 0
|
||||
|
||||
get_volume_config = mock.MagicMock(return_value=conf)
|
||||
doc = etree.fromstring(xml)
|
||||
res = etree.tostring(migration._update_volume_xml(
|
||||
doc, data, get_volume_config), encoding='unicode')
|
||||
self.assertThat(res, matchers.XMLMatches(new_xml))
|
||||
|
||||
def test_update_volume_xml_update_encryption(self):
|
||||
connection_info = {
|
||||
'driver_volume_type': 'rbd',
|
||||
'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192',
|
||||
'data': {
|
||||
'access_mode': 'rw',
|
||||
'secret_type': 'ceph',
|
||||
'name': 'cinder-volumes/volume-d299a078',
|
||||
'encrypted': False,
|
||||
'discard': True,
|
||||
'cluster_name': 'ceph',
|
||||
'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9',
|
||||
'qos_specs': None,
|
||||
'auth_enabled': True,
|
||||
'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192',
|
||||
'hosts': ['172.16.128.101', '172.16.128.121'],
|
||||
'auth_username': 'cinder',
|
||||
'ports': ['6789', '6789', '6789']}}
|
||||
bdm = objects.LibvirtLiveMigrateBDMInfo(
|
||||
serial='d299a078-f0db-4993-bf03-f10fe44fd192',
|
||||
bus='scsi', type='disk', dev='sdb',
|
||||
connection_info=connection_info,
|
||||
encryption_secret_uuid=uuids.encryption_secret_uuid_new)
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
target_connect_addr=None,
|
||||
bdms=[bdm],
|
||||
block_migration=False)
|
||||
xml = """<domain>
|
||||
<devices>
|
||||
<disk type='network' device='disk'>
|
||||
<driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
|
||||
<auth username='cinder'>
|
||||
<secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
|
||||
</auth>
|
||||
<source protocol='rbd' name='cinder-volumes/volume-d299a078'>
|
||||
<host name='172.16.128.101' port='6789'/>
|
||||
<host name='172.16.128.121' port='6789'/>
|
||||
</source>
|
||||
<backingStore/>
|
||||
<target dev='sdb' bus='scsi'/>
|
||||
<serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
|
||||
<alias name='scsi0-0-0-1'/>
|
||||
<encryption format='luks'>
|
||||
<secret type='passphrase' uuid='%(encryption_secret_uuid)s'/>
|
||||
</encryption>
|
||||
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
|
||||
</disk>
|
||||
</devices>
|
||||
</domain>""" % {'encryption_secret_uuid': uuids.encryption_secret_uuid_old}
|
||||
conf = vconfig.LibvirtConfigGuestDisk()
|
||||
conf.source_device = bdm.type
|
||||
conf.driver_name = "qemu"
|
||||
conf.driver_format = "raw"
|
||||
conf.driver_cache = "writeback"
|
||||
conf.target_dev = bdm.dev
|
||||
conf.target_bus = bdm.bus
|
||||
conf.serial = bdm.connection_info.get('serial')
|
||||
conf.source_type = "network"
|
||||
conf.driver_discard = 'unmap'
|
||||
conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive()
|
||||
conf.device_addr.controller = 0
|
||||
|
||||
get_volume_config = mock.MagicMock(return_value=conf)
|
||||
doc = etree.fromstring(xml)
|
||||
res = etree.tostring(migration._update_volume_xml(
|
||||
doc, data, get_volume_config), encoding='unicode')
|
||||
new_xml = xml.replace(uuids.encryption_secret_uuid_old,
|
||||
uuids.encryption_secret_uuid_new)
|
||||
self.assertThat(res, matchers.XMLMatches(new_xml))
|
||||
|
||||
def test_update_perf_events_xml(self):
|
||||
data = objects.LibvirtLiveMigrateData(
|
||||
supported_perf_events=['cmt'])
|
||||
|
|
|
@ -18,6 +18,7 @@ import mock
|
|||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||
from nova.tests import uuidsentinel as uuids
|
||||
from nova.virt import fake
|
||||
from nova.virt.libvirt import driver
|
||||
from nova.virt.libvirt import host
|
||||
|
@ -330,3 +331,45 @@ class LibvirtVolumeTestCase(LibvirtISCSIVolumeBaseTestCase):
|
|||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
tree = conf.format_dom()
|
||||
self.assertIsNone(tree.find("driver[@discard]"))
|
||||
|
||||
def test_libvirt_volume_driver_encryption(self):
|
||||
fake_secret = FakeSecret()
|
||||
fake_host = mock.Mock(spec=host.Host)
|
||||
fake_host.find_secret.return_value = fake_secret
|
||||
|
||||
libvirt_driver = volume.LibvirtVolumeDriver(fake_host)
|
||||
connection_info = {
|
||||
'driver_volume_type': 'fake',
|
||||
'data': {
|
||||
'volume_id': uuids.volume_id,
|
||||
'device_path': '/foo',
|
||||
'discard': False,
|
||||
},
|
||||
'serial': 'fake_serial',
|
||||
}
|
||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
tree = conf.format_dom()
|
||||
encryption = tree.find("encryption")
|
||||
secret = encryption.find("secret")
|
||||
self.assertEqual('luks', encryption.attrib['format'])
|
||||
self.assertEqual('passphrase', secret.attrib['type'])
|
||||
self.assertEqual(SECRET_UUID, secret.attrib['uuid'])
|
||||
|
||||
def test_libvirt_volume_driver_encryption_missing_secret(self):
|
||||
fake_host = mock.Mock(spec=host.Host)
|
||||
fake_host.find_secret.return_value = None
|
||||
|
||||
libvirt_driver = volume.LibvirtVolumeDriver(fake_host)
|
||||
connection_info = {
|
||||
'driver_volume_type': 'fake',
|
||||
'data': {
|
||||
'volume_id': uuids.volume_id,
|
||||
'device_path': '/foo',
|
||||
'discard': False,
|
||||
},
|
||||
'serial': 'fake_serial',
|
||||
}
|
||||
|
||||
conf = libvirt_driver.get_config(connection_info, self.disk_info)
|
||||
tree = conf.format_dom()
|
||||
self.assertIsNone(tree.find("encryption"))
|
||||
|
|
|
@ -25,6 +25,7 @@ Supports KVM, LXC, QEMU, UML, XEN and Parallels.
|
|||
|
||||
"""
|
||||
|
||||
import binascii
|
||||
import collections
|
||||
from collections import deque
|
||||
import contextlib
|
||||
|
@ -46,6 +47,7 @@ from eventlet import greenthread
|
|||
from eventlet import tpool
|
||||
from lxml import etree
|
||||
from os_brick import encryptors
|
||||
from os_brick.encryptors import luks as luks_encryptor
|
||||
from os_brick import exception as brick_exception
|
||||
from os_brick.initiator import connector
|
||||
from oslo_concurrency import processutils
|
||||
|
@ -303,6 +305,9 @@ MIN_LIBVIRT_MDEV_SUPPORT = (3, 4, 0)
|
|||
# for details.
|
||||
MIN_LIBVIRT_MULTIATTACH = (3, 10, 0)
|
||||
|
||||
MIN_LIBVIRT_LUKS_VERSION = (2, 2, 0)
|
||||
MIN_QEMU_LUKS_VERSION = (2, 6, 0)
|
||||
|
||||
|
||||
VGPU_RESOURCE_SEMAPHORE = "vgpu_resources"
|
||||
|
||||
|
@ -646,6 +651,10 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
return self._host.has_min_version(MIN_LIBVIRT_VIRTLOGD,
|
||||
MIN_QEMU_VIRTLOGD)
|
||||
|
||||
def _is_native_luks_available(self):
|
||||
return self._host.has_min_version(MIN_LIBVIRT_LUKS_VERSION,
|
||||
MIN_QEMU_LUKS_VERSION)
|
||||
|
||||
def _handle_live_migration_post_copy(self, migration_flags):
|
||||
if CONF.libvirt.live_migration_permit_post_copy:
|
||||
if self._is_post_copy_available():
|
||||
|
@ -1221,10 +1230,11 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
return self.volume_drivers[driver_type]
|
||||
|
||||
def _connect_volume(self, context, connection_info, instance,
|
||||
encryption=None):
|
||||
encryption=None, allow_native_luks=True):
|
||||
vol_driver = self._get_volume_driver(connection_info)
|
||||
vol_driver.connect_volume(connection_info, instance)
|
||||
self._attach_encryptor(context, connection_info, encryption=encryption)
|
||||
self._attach_encryptor(context, connection_info, encryption,
|
||||
allow_native_luks)
|
||||
|
||||
def _disconnect_volume(self, context, connection_info, instance,
|
||||
encryption=None):
|
||||
|
@ -1236,6 +1246,16 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
vol_driver = self._get_volume_driver(connection_info)
|
||||
return vol_driver.extend_volume(connection_info, instance)
|
||||
|
||||
def _use_native_luks(self, encryption=None):
|
||||
"""Is LUKS the required provider and native QEMU LUKS available
|
||||
"""
|
||||
provider = None
|
||||
if encryption:
|
||||
provider = encryption.get('provider', None)
|
||||
if provider in encryptors.LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP:
|
||||
provider = encryptors.LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP[provider]
|
||||
return provider == encryptors.LUKS and self._is_native_luks_available()
|
||||
|
||||
def _get_volume_config(self, connection_info, disk_info):
|
||||
vol_driver = self._get_volume_driver(connection_info)
|
||||
conf = vol_driver.get_config(connection_info, disk_info)
|
||||
|
@ -1259,16 +1279,52 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
self._volume_api, volume_id, connection_info)
|
||||
return encryption
|
||||
|
||||
def _attach_encryptor(self, context, connection_info, encryption):
|
||||
def _attach_encryptor(self, context, connection_info, encryption,
|
||||
allow_native_luks):
|
||||
"""Attach the frontend encryptor if one is required by the volume.
|
||||
|
||||
The request context is only used when an encryption metadata dict is
|
||||
not provided. The encryption metadata dict being populated is then used
|
||||
to determine if an attempt to attach the encryptor should be made.
|
||||
|
||||
If native LUKS decryption is enabled then create a Libvirt volume
|
||||
secret containing the LUKS passphrase for the volume.
|
||||
"""
|
||||
if encryption is None:
|
||||
encryption = self._get_volume_encryption(context, connection_info)
|
||||
if encryption:
|
||||
|
||||
if (encryption and allow_native_luks and
|
||||
self._use_native_luks(encryption)):
|
||||
# NOTE(lyarwood): Fetch the associated key for the volume and
|
||||
# decode the passphrase from the key.
|
||||
# FIXME(lyarwood): c-vol currently creates symmetric keys for use
|
||||
# with volumes, leading to the binary to hex to string conversion
|
||||
# below.
|
||||
keymgr = key_manager.API(CONF)
|
||||
key = keymgr.get(context, encryption['encryption_key_id'])
|
||||
key_encoded = key.get_encoded()
|
||||
passphrase = binascii.hexlify(key_encoded).decode('utf-8')
|
||||
|
||||
# NOTE(lyarwood): Retain the behaviour of the original os-brick
|
||||
# encryptors and format any volume that does not identify as
|
||||
# encrypted with LUKS.
|
||||
# FIXME(lyarwood): Remove this once c-vol correctly formats
|
||||
# encrypted volumes during their initial creation:
|
||||
# https://bugs.launchpad.net/cinder/+bug/1739442
|
||||
device_path = connection_info.get('data').get('device_path')
|
||||
if device_path:
|
||||
root_helper = utils.get_root_helper()
|
||||
if not luks_encryptor.is_luks(root_helper, device_path):
|
||||
encryptor = self._get_volume_encryptor(connection_info,
|
||||
encryption)
|
||||
encryptor._format_volume(passphrase, **encryption)
|
||||
|
||||
# NOTE(lyarwood): Store the passphrase as a libvirt secret locally
|
||||
# on the compute node. This secret is used later when generating
|
||||
# the volume config.
|
||||
volume_id = connection_info.get('data', {}).get('volume_id')
|
||||
self._host.create_secret('volume', volume_id, password=passphrase)
|
||||
elif encryption:
|
||||
encryptor = self._get_volume_encryptor(connection_info,
|
||||
encryption)
|
||||
encryptor.attach_volume(context, **encryption)
|
||||
|
@ -1279,7 +1335,13 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
The request context is only used when an encryption metadata dict is
|
||||
not provided. The encryption metadata dict being populated is then used
|
||||
to determine if an attempt to detach the encryptor should be made.
|
||||
|
||||
If native LUKS decryption is enabled then delete previously created
|
||||
Libvirt volume secret from the host.
|
||||
"""
|
||||
volume_id = connection_info.get('data', {}).get('volume_id')
|
||||
if volume_id and self._host.find_secret('volume', volume_id):
|
||||
return self._host.delete_secret('volume', volume_id)
|
||||
if encryption is None:
|
||||
encryption = self._get_volume_encryption(context, connection_info)
|
||||
if encryption:
|
||||
|
@ -1422,6 +1484,12 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
def swap_volume(self, context, old_connection_info,
|
||||
new_connection_info, instance, mountpoint, resize_to):
|
||||
|
||||
# NOTE(lyarwood): https://bugzilla.redhat.com/show_bug.cgi?id=760547
|
||||
encryption = self._get_volume_encryption(context, old_connection_info)
|
||||
if encryption and self._use_native_luks(encryption):
|
||||
raise NotImplementedError(_("Swap volume is not supported for"
|
||||
"encrypted volumes when native LUKS decryption is enabled."))
|
||||
|
||||
guest = self._host.get_guest(instance)
|
||||
|
||||
disk_dev = mountpoint.rpartition("/")[2]
|
||||
|
@ -6350,6 +6418,14 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
relative=True)
|
||||
dest_check_data.instance_relative_path = instance_path
|
||||
|
||||
# NOTE(lyarwood): Used to indicate to the dest that the src is capable
|
||||
# of wiring up the encrypted disk configuration for the domain.
|
||||
# Note that this does not require the QEMU and Libvirt versions to
|
||||
# decrypt LUKS to be installed on the source node. Only the Nova
|
||||
# utility code to generate the correct XML is required, so we can
|
||||
# default to True here for all computes >= Queens.
|
||||
dest_check_data.src_supports_native_luks = True
|
||||
|
||||
return dest_check_data
|
||||
|
||||
def _is_shared_block_storage(self, instance, dest_check_data,
|
||||
|
@ -7246,7 +7322,17 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
|
||||
for bdm in block_device_mapping:
|
||||
connection_info = bdm['connection_info']
|
||||
self._connect_volume(context, connection_info, instance)
|
||||
# NOTE(lyarwood): Handle the P to Q LM during upgrade use case
|
||||
# where an instance has encrypted volumes attached using the
|
||||
# os-brick encryptors. Do not attempt to attach the encrypted
|
||||
# volume using native LUKS decryption on the destionation.
|
||||
src_native_luks = False
|
||||
if migrate_data.obj_attr_is_set('src_supports_native_luks'):
|
||||
src_native_luks = migrate_data.src_supports_native_luks
|
||||
dest_native_luks = self._is_native_luks_available()
|
||||
allow_native_luks = src_native_luks and dest_native_luks
|
||||
self._connect_volume(context, connection_info, instance,
|
||||
allow_native_luks=allow_native_luks)
|
||||
|
||||
# We call plug_vifs before the compute manager calls
|
||||
# ensure_filtering_rules_for_instance, to ensure bridge is set up
|
||||
|
@ -7302,6 +7388,13 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
bdmi.type = disk_info['type']
|
||||
bdmi.format = disk_info.get('format')
|
||||
bdmi.boot_index = disk_info.get('boot_index')
|
||||
volume_id = connection_info.get('volume_id')
|
||||
volume_secret = None
|
||||
if volume_id:
|
||||
volume_secret = self._host.find_secret('volume', volume_id)
|
||||
if volume_secret:
|
||||
bdmi.encryption_secret_uuid = volume_secret.UUIDString()
|
||||
|
||||
migrate_data.bdms.append(bdmi)
|
||||
|
||||
return migrate_data
|
||||
|
|
|
@ -24,6 +24,7 @@ from oslo_log import log as logging
|
|||
|
||||
from nova.compute import power_state
|
||||
import nova.conf
|
||||
from nova.virt.libvirt import config as vconfig
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -148,6 +149,15 @@ def _update_volume_xml(xml_doc, migrate_data, get_volume_config):
|
|||
continue
|
||||
conf = get_volume_config(
|
||||
bdm_info.connection_info, bdm_info.as_disk_info())
|
||||
|
||||
if bdm_info.obj_attr_is_set('encryption_secret_uuid'):
|
||||
conf.encryption = vconfig.LibvirtConfigGuestDiskEncryption()
|
||||
conf.encryption.format = 'luks'
|
||||
secret = vconfig.LibvirtConfigGuestDiskEncryptionSecret()
|
||||
secret.type = 'passphrase'
|
||||
secret.uuid = bdm_info.encryption_secret_uuid
|
||||
conf.encryption.secret = secret
|
||||
|
||||
xml_doc2 = etree.XML(conf.to_xml(), parser)
|
||||
serial_dest = xml_doc2.findtext('serial')
|
||||
|
||||
|
|
|
@ -109,6 +109,18 @@ class LibvirtBaseVolumeDriver(object):
|
|||
# a shareable disk.
|
||||
conf.shareable = True
|
||||
|
||||
volume_id = connection_info.get('data', {}).get('volume_id')
|
||||
volume_secret = None
|
||||
if volume_id:
|
||||
volume_secret = self.host.find_secret('volume', volume_id)
|
||||
if volume_secret:
|
||||
conf.encryption = vconfig.LibvirtConfigGuestDiskEncryption()
|
||||
secret = vconfig.LibvirtConfigGuestDiskEncryptionSecret()
|
||||
secret.type = 'passphrase'
|
||||
secret.uuid = volume_secret.UUIDString()
|
||||
conf.encryption.format = 'luks'
|
||||
conf.encryption.secret = secret
|
||||
|
||||
return conf
|
||||
|
||||
def connect_volume(self, connection_info, instance):
|
||||
|
|
Loading…
Reference in New Issue