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:
Lee Yarwood 2020-11-13 19:26:32 +00:00 committed by melanie witt
parent bf701eb4a0
commit 2f97ca2cdc
4 changed files with 176 additions and 1 deletions

View File

@ -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
)

View File

@ -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

View File

@ -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

View File

@ -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