Merge "libvirt: QEMU native LUKS decryption for encrypted volumes"
This commit is contained in:
commit
e9ae961d92
@ -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)
|
||||
@ -15402,6 +15576,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,
|
||||
@ -15541,6 +15725,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')
|
||||
@ -15550,7 +15735,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
|
||||
"""
|
||||
@ -15558,6 +15743,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
|
||||
@ -15571,6 +15757,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')
|
||||
@ -15581,7 +15768,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
|
||||
"""
|
||||
@ -15589,6 +15777,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
|
||||
@ -15905,7 +16094,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
|
||||
@ -304,6 +306,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"
|
||||
|
||||
@ -647,6 +652,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():
|
||||
@ -1222,10 +1231,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):
|
||||
@ -1237,6 +1247,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)
|
||||
@ -1260,16 +1280,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)
|
||||
@ -1280,7 +1336,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:
|
||||
@ -1423,6 +1485,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]
|
||||
@ -6389,6 +6457,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,
|
||||
@ -7285,7 +7361,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
|
||||
@ -7341,6 +7427,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
Block a user