compute: Update bdms with ephemeral encryption details when requested
This change starts the process of wiring up the new ephemeral encryption control mechanisims in the compute layer. This initial step being to ensure the BlockDeviceMapping objects are correctly updated with the required ephemeral encryption details when requested through the instance flavor extra specs or image metadata properties. Change-Id: Id49cb238f7bbf2b97f018ddbe090ebdc08d762dc
This commit is contained in:
parent
bf701eb4a0
commit
2f97ca2cdc
|
@ -15,6 +15,7 @@
|
|||
"""Validators for ``hw`` namespaced extra specs."""
|
||||
|
||||
from nova.api.validation.extra_specs import base
|
||||
from nova.objects import fields
|
||||
|
||||
|
||||
realtime_validators = [
|
||||
|
@ -500,6 +501,31 @@ feature_flag_validators = [
|
|||
),
|
||||
]
|
||||
|
||||
ephemeral_encryption_validators = [
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:ephemeral_encryption',
|
||||
description=(
|
||||
'Whether to enable ephemeral storage encryption.'
|
||||
),
|
||||
value={
|
||||
'type': bool,
|
||||
'description': 'Whether to enable ephemeral storage encryption.',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:ephemeral_encryption_format',
|
||||
description=(
|
||||
'The encryption format to be used if ephemeral storage '
|
||||
'encryption is enabled via hw:ephemeral_encryption.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'description': 'The encryption format to be used if enabled.',
|
||||
'enum': fields.BlockDeviceEncryptionFormatType.ALL,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
return (
|
||||
|
@ -509,5 +535,6 @@ def register():
|
|||
hugepage_validators +
|
||||
numa_validators +
|
||||
cpu_topology_validators +
|
||||
feature_flag_validators
|
||||
feature_flag_validators +
|
||||
ephemeral_encryption_validators
|
||||
)
|
||||
|
|
|
@ -1581,6 +1581,42 @@ class API:
|
|||
|
||||
return objects.InstanceGroup.get_by_uuid(context, group_hint)
|
||||
|
||||
def _update_ephemeral_encryption_bdms(
|
||||
self,
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta_dict: ty.Dict[str, ty.Any],
|
||||
block_device_mapping: 'objects.BlockDeviceMappingList',
|
||||
) -> None:
|
||||
"""Update local BlockDeviceMappings when ephemeral encryption requested
|
||||
|
||||
Enable ephemeral encryption in all local BlockDeviceMappings
|
||||
when requested in the flavor or image. Also optionally set the format
|
||||
and options if also provided.
|
||||
|
||||
:param flavor: The instance flavor for the request
|
||||
:param image_meta_dict: The image metadata for the request
|
||||
:block_device_mapping: The current block_device_mapping for the request
|
||||
"""
|
||||
image_meta = _get_image_meta_obj(image_meta_dict)
|
||||
if not hardware.get_ephemeral_encryption_constraint(
|
||||
flavor, image_meta):
|
||||
return
|
||||
|
||||
# NOTE(lyarwood): Attempt to find the format in the flavor and image,
|
||||
# if one isn't found then the compute will need to provide and save a
|
||||
# default format during a the initial build.
|
||||
eph_format = hardware.get_ephemeral_encryption_format(
|
||||
flavor, image_meta)
|
||||
|
||||
# NOTE(lyarwood): The term ephemeral is overloaded in the codebase,
|
||||
# what it actually means in the context of ephemeral encryption is
|
||||
# anything local to the compute host so use the is_local property.
|
||||
# TODO(lyarwood): Add .get_local_devices() to BlockDeviceMappingList
|
||||
for bdm in [b for b in block_device_mapping if b.is_local]:
|
||||
bdm.encrypted = True
|
||||
if eph_format:
|
||||
bdm.encryption_format = eph_format
|
||||
|
||||
def _create_instance(self, context, flavor,
|
||||
image_href, kernel_id, ramdisk_id,
|
||||
min_count, max_count,
|
||||
|
@ -1658,10 +1694,17 @@ class API:
|
|||
'max_net_count': max_net_count})
|
||||
max_count = max_net_count
|
||||
|
||||
# _check_and_transform_bdm transforms block_device_mapping from API
|
||||
# bdms (dicts) to a BlockDeviceMappingList.
|
||||
block_device_mapping = self._check_and_transform_bdm(context,
|
||||
base_options, flavor, boot_meta, min_count, max_count,
|
||||
block_device_mapping, legacy_bdm)
|
||||
|
||||
# Update any local BlockDeviceMapping objects if ephemeral encryption
|
||||
# has been requested though flavor extra specs or image properties
|
||||
self._update_ephemeral_encryption_bdms(
|
||||
flavor, boot_meta, block_device_mapping)
|
||||
|
||||
# We can't do this check earlier because we need bdms from all sources
|
||||
# to have been merged in order to get the root bdm.
|
||||
# Set validate_numa=False since numa validation is already done by
|
||||
|
|
|
@ -5917,6 +5917,41 @@ class _ComputeAPIUnitTestMixIn(object):
|
|||
'volume_id': 'volume_id'}]
|
||||
self._test_check_and_transform_bdm(block_device_mapping)
|
||||
|
||||
def test_update_ephemeral_encryption_bdms(self):
|
||||
flavor = self._create_flavor(
|
||||
extra_specs={
|
||||
'hw:ephemeral_encryption': True,
|
||||
'hw:ephemeral_encryption_format': 'luks',
|
||||
}
|
||||
)
|
||||
block_device_mapping = [
|
||||
{'device_name': '/dev/sda1',
|
||||
'source_type': 'snapshot', 'destination_type': 'volume',
|
||||
'snapshot_id': uuids.snapshot_id,
|
||||
'delete_on_termination': False,
|
||||
'boot_index': 0},
|
||||
{'device_name': '/dev/sdb2',
|
||||
'source_type': 'image', 'destination_type': 'local',
|
||||
'image_id': uuids.image_id, 'delete_on_termination': False},
|
||||
{'device_name': '/dev/sdb3',
|
||||
'source_type': 'blank', 'destination_type': 'local',
|
||||
'guest_format': 'ext3', 'delete_on_termination': False}]
|
||||
|
||||
block_device_mapping = (
|
||||
block_device_obj.block_device_make_list_from_dicts(
|
||||
self.context,
|
||||
map(fake_block_device.AnonFakeDbBlockDeviceDict,
|
||||
block_device_mapping)))
|
||||
|
||||
self.compute_api._update_ephemeral_encryption_bdms(
|
||||
flavor, {}, block_device_mapping)
|
||||
|
||||
for bdm in block_device_mapping:
|
||||
if bdm.is_local:
|
||||
self.assertTrue(bdm.encrypted)
|
||||
else:
|
||||
self.assertFalse(bdm.encrypted)
|
||||
|
||||
def test_bdm_validate_set_size_and_instance(self):
|
||||
swap_size = 42
|
||||
ephemeral_size = 24
|
||||
|
|
|
@ -2566,3 +2566,73 @@ def check_hw_rescue_props(image_meta):
|
|||
"""
|
||||
hw_rescue_props = ['hw_rescue_device', 'hw_rescue_bus']
|
||||
return any(key in image_meta.properties for key in hw_rescue_props)
|
||||
|
||||
|
||||
def get_ephemeral_encryption_constraint(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> bool:
|
||||
"""Get the ephemeral encryption constrants based on the flavor and image.
|
||||
|
||||
:param flavor: an objects.Flavor object
|
||||
:param image_meta: an objects.ImageMeta object
|
||||
:raises: nova.exception.FlavorImageConflict
|
||||
:returns: boolean indicating whether encryption of guest ephemeral storage
|
||||
was requested
|
||||
"""
|
||||
flavor_eph_encryption_str, image_eph_encryption = _get_flavor_image_meta(
|
||||
'ephemeral_encryption', flavor, image_meta)
|
||||
|
||||
flavor_eph_encryption = None
|
||||
if flavor_eph_encryption_str is not None:
|
||||
flavor_eph_encryption = strutils.bool_from_string(
|
||||
flavor_eph_encryption_str)
|
||||
|
||||
# Check for conflicts between explicit requirements regarding
|
||||
# ephemeral encryption.
|
||||
# TODO(layrwood): make _check_for_mem_encryption_requirement_conflicts
|
||||
# generic and reuse here
|
||||
if (
|
||||
flavor_eph_encryption is not None and
|
||||
image_eph_encryption is not None and
|
||||
flavor_eph_encryption != image_eph_encryption
|
||||
):
|
||||
emsg = _(
|
||||
"Flavor %(flavor_name)s has hw:ephemeral_encryption extra spec "
|
||||
"explicitly set to %(flavor_val)s, conflicting with "
|
||||
"image %(image_name)s which has hw_eph_encryption property "
|
||||
"explicitly set to %(image_val)s"
|
||||
)
|
||||
data = {
|
||||
'flavor_name': flavor.name,
|
||||
'flavor_val': flavor_eph_encryption_str,
|
||||
'image_name': image_meta.name,
|
||||
'image_val': image_eph_encryption,
|
||||
}
|
||||
raise exception.FlavorImageConflict(emsg % data)
|
||||
|
||||
return flavor_eph_encryption or image_eph_encryption
|
||||
|
||||
|
||||
def get_ephemeral_encryption_format(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> ty.Optional[str]:
|
||||
"""Get the ephemeral encryption format.
|
||||
|
||||
:param flavor: an objects.Flavor object
|
||||
:param image_meta: an objects.ImageMeta object
|
||||
:raises: nova.exception.FlavorImageConflict or nova.exception.Invalid
|
||||
:returns: BlockDeviceEncryptionFormatType or None
|
||||
"""
|
||||
eph_format = _get_unique_flavor_image_meta(
|
||||
'ephemeral_encryption_format', flavor, image_meta)
|
||||
if eph_format:
|
||||
if eph_format not in fields.BlockDeviceEncryptionFormatType.ALL:
|
||||
allowed = fields.BlockDeviceEncryptionFormatType.ALL
|
||||
raise exception.Invalid(
|
||||
f"Invalid ephemeral encryption format {eph_format}. "
|
||||
f"Allowed values: {', '.join(allowed)}"
|
||||
)
|
||||
return eph_format
|
||||
return None
|
||||
|
|
Loading…
Reference in New Issue