Add extra spec parameter and image property for memory encryption

Add a new "hw:mem_encryption" extra spec parameter, and a new
"hw_mem_encryption" image property, which indicate that any guest
booted with that extra spec parameter or image property respectively
needs to be booted with its memory hardware-encrypted.

This is achieved by converting the requirement stated in the extra
spec parameter and/or image property into an additional extra spec
parameter which requests resources for one slot of the inventory of
the new MEM_ENCRYPTION_CONTEXT resource class (introduced in
os-resource-classes 0.4.0).  The inventory will be provided by the
follow-up commit I659cb77f12a38a4d2fb118530ebb9de88d2ed30d.

Since future commits adding support for SEV to guest XML config will
also need to know at launch-time whether memory encryption has been
requested, add a reusable mem_encryption_requested() function to the
nova.virt.hardware library for detecting which of the extra spec /
image property (if either) have requested encrypted memory.

If both the extra spec parameter and the image property are explicitly
specified and they contradict each other, or if either request memory
encryption but the image does not have hw_firmware_type set to UEFI,
then log an error and raise a new generic FlavorImageConflict
exception.  This exception can also be useful in the future for
handling other similar conflicts.  In this particular use case,
FlavorImageConflict is raised by mem_encryption_requested(), and then
if caught during API call validation, it's re-raised as
HTTPBadRequest.

In order to test this code, we need to construct various ImageMeta
objects containing fake data and a ImageMetaProps instance for each.
This is a slightly fiddly task which future patches in the SEV series
will also need to perform, so add a helper to nova.tests.unit.image.fake
for this.

blueprint: amd-sev-libvirt-support
Change-Id: I8c63b5cc5ad97ce831adb2eb96a995ebc798ecb7
This commit is contained in:
Adam Spiers 2019-06-10 18:15:40 +01:00
parent 160d1042b4
commit b4905467db
13 changed files with 572 additions and 16 deletions

View File

@ -66,7 +66,7 @@ numpy==1.14.2
openstacksdk==0.35.0
os-brick==2.6.1
os-client-config==1.29.0
os-resource-classes==0.1.0
os-resource-classes==0.4.0
os-service-types==1.7.0
os-traits==0.16.0
os-vif==1.14.0

View File

@ -57,6 +57,7 @@ LOG = logging.getLogger(__name__)
INVALID_FLAVOR_IMAGE_EXCEPTIONS = (
exception.BadRequirementEmulatorThreadsPolicy,
exception.CPUThreadPolicyConfigurationInvalid,
exception.FlavorImageConflict,
exception.ImageCPUPinningForbidden,
exception.ImageCPUThreadPolicyForbidden,
exception.ImageNUMATopologyAsymmetric,

View File

@ -676,6 +676,8 @@ class API(base.Base):
"""
image_meta = _get_image_meta_obj(image)
API._validate_flavor_image_mem_encryption(instance_type, image_meta)
# validate PMU extra spec and image metadata
flavor_pmu = instance_type.extra_specs.get('hw:pmu')
image_pmu = image_meta.properties.get('hw_pmu')
@ -694,6 +696,19 @@ class API(base.Base):
if validate_pci:
pci_request.get_pci_requests_from_flavor(instance_type)
@staticmethod
def _validate_flavor_image_mem_encryption(instance_type, image):
"""Validate that the flavor and image don't make contradictory
requests regarding memory encryption.
:param instance_type: Flavor object
:param image: an ImageMeta object
:raises: nova.exception.FlavorImageConflict
"""
# This library function will raise the exception for us if
# necessary; if not, we can ignore the result returned.
hardware.get_mem_encryption_constraint(instance_type, image)
def _get_image_defined_bdms(self, instance_type, image_meta,
root_device_name):
image_properties = image_meta.get('properties', {})

View File

@ -2450,6 +2450,11 @@ class ReshapeNeeded(NovaException):
"moved.")
class FlavorImageConflict(NovaException):
msg_fmt = _("Conflicting values for %(setting)s found in the flavor "
"(%(flavor_val)s) and the image (%(image_val)s).")
class HealPortAllocationException(NovaException):
msg_fmt = _("Healing port allocation failed.")

View File

@ -173,12 +173,15 @@ class ImageMetaProps(base.NovaObject):
# Version 1.21: Added 'hw_time_hpet' field
# Version 1.22: Added 'gop', 'virtio' and 'none' to hw_video_model field
# Version 1.23: Added 'hw_pmu' field
VERSION = '1.23'
# Version 1.24: Added 'hw_mem_encryption' field
VERSION = '1.24'
def obj_make_compatible(self, primitive, target_version):
super(ImageMetaProps, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 24):
primitive.pop('hw_mem_encryption', None)
if target_version < (1, 23):
primitive.pop('hw_pmu', None)
# NOTE(sean-k-mooney): unlike other nova object we version this object
@ -312,6 +315,10 @@ class ImageMetaProps(base.NovaObject):
# form string
'hw_machine_type': fields.StringField(),
# boolean indicating that the guest needs to be booted with
# encrypted memory
'hw_mem_encryption': fields.FlexibleBooleanField(),
# One of the magic strings 'small', 'any', 'large'
# or an explicit page size in KB (eg 4, 2048, ...)
'hw_mem_page_size': fields.StringField(),

View File

@ -35,6 +35,7 @@ from nova.objects import base as obj_base
from nova.objects import instance as obj_instance
from nova import rpc
from nova.scheduler.filters import utils as filters_utils
import nova.virt.hardware as hw
LOG = logging.getLogger(__name__)
@ -90,13 +91,15 @@ class ResourceRequest(object):
# TODO(efried): Handle member_of[$N], which will need to be reconciled
# with destination.aggregates handling in resources_from_request_spec
image = (request_spec.image if 'image' in request_spec
else objects.ImageMeta(properties=objects.ImageMetaProps()))
# Parse the flavor extra specs
self._process_extra_specs(request_spec.flavor)
self.numbered_groups_from_flavor = self.get_num_of_numbered_groups()
# Now parse the (optional) image metadata
image = request_spec.image if 'image' in request_spec else None
self._process_image_meta(image)
# Finally, parse the flavor itself, though we'll only use these fields
@ -119,6 +122,8 @@ class ResourceRequest(object):
if disk:
self._add_resource(None, orc.DISK_GB, disk)
self._translate_memory_encryption(request_spec.flavor, image)
self.strip_zeros()
def _process_extra_specs(self, flavor):
@ -157,6 +162,23 @@ class ResourceRequest(object):
# supported in image traits
self._add_trait(None, trait, "required")
def _translate_memory_encryption(self, flavor, image):
"""When the hw:mem_encryption extra spec or the hw_mem_encryption
image property are requested, translate into a request for
resources:MEM_ENCRYPTION_CONTEXT=1 which requires a slot on a
host which can support encryption of the guest memory.
"""
# NOTE(aspiers): In theory this could raise FlavorImageConflict,
# but we already check it in the API layer, so that should never
# happen.
if not hw.get_mem_encryption_constraint(flavor, image):
# No memory encryption required, so no further action required.
return
self._add_resource(None, orc.MEM_ENCRYPTION_CONTEXT, 1)
LOG.debug("Added %s=1 to requested resources",
orc.MEM_ENCRYPTION_CONTEXT)
@property
def group_policy(self):
return self._group_policy

View File

@ -6131,6 +6131,15 @@ class ServersControllerCreateTest(test.TestCase):
self.controller.create,
self.req, body=self.body)
@mock.patch('nova.virt.hardware.get_mem_encryption_constraint',
side_effect=exception.FlavorImageConflict(
message="fake conflict reason"))
def test_create_instance_raise_flavor_image_conflict(
self, mock_conflict):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create,
self.req, body=self.body)
@mock.patch('nova.virt.hardware.numa_get_constraints',
side_effect=exception.ImageCPUPinningForbidden())
def test_create_instance_raise_image_cpu_pinning_forbidden(

View File

@ -24,6 +24,7 @@ from oslo_utils import uuidutils
import nova.conf
from nova import exception
from nova import objects
from nova.objects import fields as obj_fields
from nova.tests import fixtures as nova_fixtures
@ -323,3 +324,28 @@ def stub_out_image_service(test):
test.useFixture(nova_fixtures.ConfPatcher(
group="glance", api_servers=['http://localhost:9292']))
return image_service
def fake_image_obj(default_image_meta=None, default_image_props=None,
variable_image_props=None):
"""Helper for constructing a test ImageMeta object with attributes and
properties coming from a combination of (probably hard-coded)
values within a test, and (optionally) variable values from the
test's caller, if the test is actually a helper written to be
reusable and run multiple times with different parameters from
different "wrapper" tests.
"""
image_meta_props = default_image_props or {}
if variable_image_props:
image_meta_props.update(variable_image_props)
test_image_meta = default_image_meta or {"disk_format": "raw"}
if 'name' not in test_image_meta:
# NOTE(aspiers): the name is specified here in case it's needed
# by the logging in nova.virt.hardware.get_mem_encryption_constraint()
test_image_meta['name'] = 'fake_image'
test_image_meta.update({
'properties': image_meta_props,
})
return objects.ImageMeta.from_dict(test_image_meta)

View File

@ -1069,7 +1069,7 @@ object_data = {
'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0',
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
'ImageMetaProps': '1.23-ed659d0bb5dfb3b2c2c717850c732abc',
'ImageMetaProps': '1.24-f92fa09d54185499da98f5430524964e',
'Instance': '2.6-5fefbcb483703c85e4d328b887c8af33',
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
'InstanceActionEvent': '1.3-c749e1b3589e7117c81cb2aa6ac438d5',

View File

@ -12,6 +12,7 @@
import ddt
import mock
import os_resource_classes as orc
from oslo_utils.fixture import uuidsentinel as uuids
import six
@ -38,11 +39,9 @@ class FakeResourceRequest(object):
self._limit = 1000
@ddt.ddt
class TestUtils(test.NoDBTestCase):
class TestUtilsBase(test.NoDBTestCase):
def setUp(self):
super(TestUtils, self).setUp()
super(TestUtilsBase, self).setUp()
self.context = nova_context.get_admin_context()
self.mock_host_manager = mock.Mock()
@ -55,6 +54,9 @@ class TestUtils(test.NoDBTestCase):
for ident in ex_by_id:
self.assertEqual(vars(ex_by_id[ident]), vars(ob_by_id[ident]))
@ddt.ddt
class TestUtils(TestUtilsBase):
@staticmethod
def _get_image_with_traits():
image_prop = {
@ -67,8 +69,9 @@ class TestUtils(test.NoDBTestCase):
image = objects.ImageMeta.from_dict(image_prop)
return image
def _test_resources_from_request_spec(self, expected, flavor,
image=objects.ImageMeta()):
def _test_resources_from_request_spec(self, expected, flavor, image=None):
if image is None:
image = objects.ImageMeta(properties=objects.ImageMetaProps())
fake_spec = objects.RequestSpec(flavor=flavor, image=image)
resources = utils.resources_from_request_spec(
self.context, fake_spec, self.mock_host_manager)
@ -1161,6 +1164,190 @@ class TestUtils(test.NoDBTestCase):
)
class TestEncryptedMemoryTranslation(TestUtilsBase):
flavor_name = 'm1.test'
image_name = 'cirros'
def _get_request_spec(self, extra_specs, image):
flavor = objects.Flavor(name=self.flavor_name,
vcpus=1,
memory_mb=1024,
root_gb=10,
ephemeral_gb=5,
swap=0,
extra_specs=extra_specs)
# NOTE(aspiers): RequestSpec.flavor is not nullable, but
# RequestSpec.image is.
reqspec = objects.RequestSpec(flavor=flavor)
if image:
reqspec.image = image
return reqspec
def _get_resource_request(self, extra_specs, image):
reqspec = self._get_request_spec(extra_specs, image)
return utils.ResourceRequest(reqspec)
def _get_expected_resource_request(self, mem_encryption_context):
expected_resources = {
'VCPU': 1,
'MEMORY_MB': 1024,
'DISK_GB': 15,
}
if mem_encryption_context:
expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
resources=expected_resources)
return expected
def _test_encrypted_memory_support_not_required(self, extra_specs,
image=None):
resreq = self._get_resource_request(extra_specs, image)
expected = self._get_expected_resource_request(False)
self.assertResourceRequestsEqual(expected, resreq)
def test_encrypted_memory_support_empty_extra_specs(self):
self._test_encrypted_memory_support_not_required(extra_specs={})
def test_encrypted_memory_support_false_extra_spec(self):
for extra_spec in ('0', 'false', 'False'):
self._test_encrypted_memory_support_not_required(
extra_specs={'hw:mem_encryption': extra_spec})
def test_encrypted_memory_support_empty_image_props(self):
self._test_encrypted_memory_support_not_required(
extra_specs={},
image=objects.ImageMeta(properties=objects.ImageMetaProps()))
def test_encrypted_memory_support_false_image_prop(self):
for image_prop in ('0', 'false', 'False'):
self._test_encrypted_memory_support_not_required(
extra_specs={},
image=objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_mem_encryption=image_prop))
)
def test_encrypted_memory_support_both_false(self):
for extra_spec in ('0', 'false', 'False'):
for image_prop in ('0', 'false', 'False'):
self._test_encrypted_memory_support_not_required(
extra_specs={'hw:mem_encryption': extra_spec},
image=objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_mem_encryption=image_prop))
)
def _test_encrypted_memory_support_conflict(self, extra_spec,
image_prop_in,
image_prop_out):
# NOTE(aspiers): hw_mem_encryption image property is a
# FlexibleBooleanField, so the result should always be coerced
# to a boolean.
self.assertIsInstance(image_prop_out, bool)
image = objects.ImageMeta(
name=self.image_name,
properties=objects.ImageMetaProps(
hw_mem_encryption=image_prop_in)
)
reqspec = self._get_request_spec(
extra_specs={'hw:mem_encryption': extra_spec},
image=image)
# Sanity check that our test request spec has an extra_specs
# dict, which is needed in order for there to be a conflict.
self.assertIn('flavor', reqspec)
self.assertIn('extra_specs', reqspec.flavor)
error = (
"Flavor %(flavor_name)s has hw:mem_encryption extra spec "
"explicitly set to %(flavor_val)s, conflicting with "
"image %(image_name)s which has hw_mem_encryption property "
"explicitly set to %(image_val)s"
)
exc = self.assertRaises(
exception.FlavorImageConflict,
utils.ResourceRequest, reqspec
)
error_data = {
'flavor_name': self.flavor_name,
'flavor_val': extra_spec,
'image_name': self.image_name,
'image_val': image_prop_out,
}
self.assertEqual(error % error_data, str(exc))
def test_encrypted_memory_support_conflict1(self):
for extra_spec in ('0', 'false', 'False'):
for image_prop_in in ('1', 'true', 'True'):
self._test_encrypted_memory_support_conflict(
extra_spec, image_prop_in, True
)
def test_encrypted_memory_support_conflict2(self):
for extra_spec in ('1', 'true', 'True'):
for image_prop_in in ('0', 'false', 'False'):
self._test_encrypted_memory_support_conflict(
extra_spec, image_prop_in, False
)
@mock.patch.object(utils, 'LOG')
def _test_encrypted_memory_support_required(self, requesters, extra_specs,
mock_log, image=None):
resreq = self._get_resource_request(extra_specs, image)
expected = self._get_expected_resource_request(True)
self.assertResourceRequestsEqual(expected, resreq)
mock_log.debug.assert_has_calls([
mock.call('Added %s=1 to requested resources',
orc.MEM_ENCRYPTION_CONTEXT)
])
def test_encrypted_memory_support_extra_spec(self):
for extra_spec in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
'hw:mem_encryption extra spec',
{'hw:mem_encryption': extra_spec},
image=objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_firmware_type='uefi'))
)
def test_encrypted_memory_support_image_prop(self):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
'hw_mem_encryption image property',
{},
image=objects.ImageMeta(
name=self.image_name,
properties=objects.ImageMetaProps(
hw_firmware_type='uefi',
hw_mem_encryption=image_prop))
)
def test_encrypted_memory_support_both_required(self):
for extra_spec in ('1', 'true', 'True'):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
'hw:mem_encryption extra spec and '
'hw_mem_encryption image property',
{'hw:mem_encryption': extra_spec},
image=objects.ImageMeta(
name=self.image_name,
properties=objects.ImageMetaProps(
hw_firmware_type='uefi',
hw_mem_encryption=image_prop))
)
class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
"""These test cases assert what happens when the group policy is missing
from the flavor but more than one numbered request group is requested from
@ -1190,6 +1377,7 @@ class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
"required": ["CUSTOM_PHYSNET_3",
"CUSTOM_VNIC_TYPE_DIRECT"]
})
self.image = objects.ImageMeta(properties=objects.ImageMetaProps())
def test_one_group_from_flavor_dont_warn(self):
flavor = objects.Flavor(
@ -1198,7 +1386,7 @@ class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
'resources1:CUSTOM_BAR': '2',
})
request_spec = objects.RequestSpec(
flavor=flavor, image=objects.ImageMeta(), requested_resources=[])
flavor=flavor, image=self.image, requested_resources=[])
rr = utils.resources_from_request_spec(
self.context, request_spec, host_manager=mock.Mock())
@ -1220,7 +1408,7 @@ class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={})
request_spec = objects.RequestSpec(
flavor=flavor, image=objects.ImageMeta(),
flavor=flavor, image=self.image,
requested_resources=[self.port_group1])
rr = utils.resources_from_request_spec(
@ -1246,7 +1434,7 @@ class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
'resources2:CUSTOM_FOO': '1'
})
request_spec = objects.RequestSpec(
flavor=flavor, image=objects.ImageMeta(), requested_resources=[])
flavor=flavor, image=self.image, requested_resources=[])
rr = utils.resources_from_request_spec(
self.context, request_spec, host_manager=mock.Mock())
@ -1270,7 +1458,7 @@ class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
'resources1:CUSTOM_BAR': '2',
})
request_spec = objects.RequestSpec(
flavor=flavor, image=objects.ImageMeta(),
flavor=flavor, image=self.image,
requested_resources=[self.port_group1])
rr = utils.resources_from_request_spec(
@ -1293,7 +1481,7 @@ class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={})
request_spec = objects.RequestSpec(
flavor=flavor, image=objects.ImageMeta(),
flavor=flavor, image=self.image,
requested_resources=[self.port_group1, self.port_group2])
rr = utils.resources_from_request_spec(

View File

@ -23,6 +23,7 @@ from nova.objects import fields
from nova.pci import stats
from nova import test
from nova.tests.unit import fake_pci_device_pools as fake_pci
from nova.tests.unit.image.fake import fake_image_obj
from nova.virt import hardware as hw
@ -3430,3 +3431,195 @@ class NetworkRequestSupportTestCase(test.NoDBTestCase):
supports = hw._numa_cells_support_network_metadata(
self.host, self.host.cells, network_metadata)
self.assertTrue(supports)
class MemEncryptionNotRequiredTestCase(test.NoDBTestCase):
def _test_encrypted_memory_support_not_required(self, flavor=None,
image_meta=None):
if flavor is None:
flavor = objects.Flavor(extra_specs={})
if image_meta is None:
image_meta = objects.ImageMeta(properties=objects.ImageMetaProps())
self.assertFalse(hw.get_mem_encryption_constraint(flavor, image_meta))
def test_requirement_disabled(self):
self._test_encrypted_memory_support_not_required()
def test_require_false_extra_spec(self):
for extra_spec in ('0', 'false', 'False'):
self._test_encrypted_memory_support_not_required(
objects.Flavor(extra_specs={'hw:mem_encryption': extra_spec})
)
def test_require_false_image_prop(self):
for image_prop in ('0', 'false', 'False'):
self._test_encrypted_memory_support_not_required(
image_meta=objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_mem_encryption=image_prop))
)
def test_require_both_false(self):
for extra_spec in ('0', 'false', 'False'):
for image_prop in ('0', 'false', 'False'):
self._test_encrypted_memory_support_not_required(
objects.Flavor(
extra_specs={'hw:mem_encryption': extra_spec}),
objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_mem_encryption=image_prop))
)
class MemEncryptionFlavorImageConflictTestCase(test.NoDBTestCase):
def _test_encrypted_memory_support_conflict(self, extra_spec,
image_prop_in, image_prop_out):
# NOTE(aspiers): hw_mem_encryption image property is a
# FlexibleBooleanField, so the result should always be coerced
# to a boolean.
self.assertIsInstance(image_prop_out, bool)
flavor_name = 'm1.faketiny'
image_name = 'fakecirros'
flavor = objects.Flavor(
name=flavor_name,
extra_specs={'hw:mem_encryption': extra_spec}
)
image_meta = objects.ImageMeta(
name=image_name,
properties=objects.ImageMetaProps(
hw_mem_encryption=image_prop_in)
)
error = (
"Flavor %(flavor_name)s has hw:mem_encryption extra spec "
"explicitly set to %(flavor_val)s, conflicting with "
"image %(image_name)s which has hw_mem_encryption property "
"explicitly set to %(image_val)s"
)
exc = self.assertRaises(
exception.FlavorImageConflict,
hw.get_mem_encryption_constraint,
flavor, image_meta
)
error_data = {
'flavor_name': flavor_name,
'flavor_val': extra_spec,
'image_name': image_name,
'image_val': image_prop_out,
}
self.assertEqual(error % error_data, str(exc))
def test_require_encrypted_memory_support_conflict1(self):
for extra_spec in ('0', 'false', 'False'):
for image_prop_in in ('1', 'true', 'True'):
self._test_encrypted_memory_support_conflict(
extra_spec, image_prop_in, True
)
def test_require_encrypted_memory_support_conflict2(self):
for extra_spec in ('1', 'true', 'True'):
for image_prop_in in ('0', 'false', 'False'):
self._test_encrypted_memory_support_conflict(
extra_spec, image_prop_in, False
)
class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
flavor_name = 'm1.faketiny'
image_name = 'fakecirros'
def _test_encrypted_memory_support_no_uefi(self, extra_spec, image_prop,
requesters):
extra_specs = {}
if extra_spec:
extra_specs['hw:mem_encryption'] = extra_spec
flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
image_meta = fake_image_obj(
{'name': self.image_name}, {'hw_firmware_type': 'bios'},
{'hw_mem_encryption': True} if image_prop else {})
error = (
"Memory encryption requested by %(requesters)s but image "
"%(image_name)s doesn't have 'hw_firmware_type' property "
"set to 'uefi'"
)
exc = self.assertRaises(
exception.FlavorImageConflict,
hw.get_mem_encryption_constraint,
flavor, image_meta
)
error_data = {'requesters': requesters,
'image_name': self.image_name}
self.assertEqual(error % error_data, str(exc))
def test_flavor_requires_encrypted_memory_support_no_uefi(self):
for extra_spec in ('1', 'true', 'True'):
self._test_encrypted_memory_support_no_uefi(
extra_spec, None,
"hw:mem_encryption extra spec in %s flavor" % self.flavor_name)
def test_image_requires_encrypted_memory_support_no_uefi(self):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_no_uefi(
None, image_prop,
"hw_mem_encryption property of image %s" % self.image_name)
def test_flavor_image_require_encrypted_memory_support_no_uefi(self):
for extra_spec in ('1', 'true', 'True'):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_no_uefi(
extra_spec, image_prop,
"hw:mem_encryption extra spec in %s flavor and "
"hw_mem_encryption property of image %s"
% (self.flavor_name, self.image_name))
class MemEncryptionRequiredTestCase(test.NoDBTestCase):
flavor_name = "m1.faketiny"
image_name = 'fakecirros'
@mock.patch.object(hw, 'LOG')
def _test_encrypted_memory_support_required(self, extra_specs,
image_props,
requesters, mock_log):
flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
image_meta = objects.ImageMeta(name=self.image_name,
properties=image_props)
self.assertTrue(hw.get_mem_encryption_constraint(flavor, image_meta))
mock_log.debug.assert_has_calls([
mock.call("Memory encryption requested by %s", requesters)
])
def test_require_encrypted_memory_support_extra_spec(self):
for extra_spec in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
{'hw:mem_encryption': extra_spec},
objects.ImageMetaProps(hw_firmware_type='uefi'),
"hw:mem_encryption extra spec in %s flavor" % self.flavor_name
)
def test_require_encrypted_memory_support_image_prop(self):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
{},
objects.ImageMetaProps(
hw_mem_encryption=image_prop,
hw_firmware_type='uefi'),
"hw_mem_encryption property of image %s" % self.image_name
)
def test_require_encrypted_memory_support_both_required(self):
for extra_spec in ('1', 'true', 'True'):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
{'hw:mem_encryption': extra_spec},
objects.ImageMetaProps(
hw_mem_encryption=image_prop,
hw_firmware_type='uefi'),
"hw:mem_encryption extra spec in %s flavor and "
"hw_mem_encryption property of image %s" %
(self.flavor_name, self.image_name)
)

View File

@ -1137,6 +1137,96 @@ def _get_flavor_image_meta(key, flavor, image_meta, default=None):
return flavor_policy, image_policy
def get_mem_encryption_constraint(flavor, image_meta):
"""Return a boolean indicating whether encryption of guest memory was
requested, either via the hw:mem_encryption extra spec or the
hw_mem_encryption image property (or both).
Also watch out for contradictory requests between the flavor and
image regarding memory encryption, and raise an exception where
encountered. These conflicts can arise in two different ways:
1) the flavor requests memory encryption but the image
explicitly requests *not* to have memory encryption, or
vice-versa
2) the flavor and/or image request memory encryption, but the
image is missing hw_firmware_type=uefi
:param instance_type: Flavor object
:param image: an ImageMeta object
:raises: nova.exception.FlavorImageConflict
:returns: boolean indicating whether encryption of guest memory
was requested
"""
flavor_mem_enc_str, image_mem_enc = _get_flavor_image_meta(
'mem_encryption', flavor, image_meta)
flavor_mem_enc = None
if flavor_mem_enc_str is not None:
flavor_mem_enc = strutils.bool_from_string(flavor_mem_enc_str)
# Image property is a FlexibleBooleanField, so coercion to a
# boolean is handled automatically
if not flavor_mem_enc and not image_mem_enc:
return False
_check_for_mem_encryption_requirement_conflicts(
flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta)
# If we get this far, either the extra spec or image property explicitly
# specified a requirement regarding memory encryption, and if both did,
# they are asking for the same thing.
requesters = []
if flavor_mem_enc:
requesters.append("hw:mem_encryption extra spec in %s flavor" %
flavor.name)
if image_mem_enc:
requesters.append("hw_mem_encryption property of image %s" %
image_meta.name)
_check_mem_encryption_uses_uefi_image(requesters, image_meta)
LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
return True
def _check_for_mem_encryption_requirement_conflicts(
flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta):
# Check for conflicts between explicit requirements regarding
# memory encryption.
if (flavor_mem_enc is not None and image_mem_enc is not None and
flavor_mem_enc != image_mem_enc):
emsg = _(
"Flavor %(flavor_name)s has hw:mem_encryption extra spec "
"explicitly set to %(flavor_val)s, conflicting with "
"image %(image_name)s which has hw_mem_encryption property "
"explicitly set to %(image_val)s"
)
data = {
'flavor_name': flavor.name,
'flavor_val': flavor_mem_enc_str,
'image_name': image_meta.name,
'image_val': image_mem_enc,
}
raise exception.FlavorImageConflict(emsg % data)
def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
if image_meta.properties.hw_firmware_type == 'uefi':
return
emsg = _(
"Memory encryption requested by %(requesters)s but image "
"%(image_name)s doesn't have 'hw_firmware_type' property set to 'uefi'"
)
data = {'requesters': " and ".join(requesters),
'image_name': image_meta.name}
raise exception.FlavorImageConflict(emsg % data)
def _get_numa_pagesize_constraint(flavor, image_meta):
"""Return the requested memory page size

View File

@ -55,7 +55,7 @@ oslo.middleware>=3.31.0 # Apache-2.0
psutil>=3.2.2 # BSD
oslo.versionedobjects>=1.35.0 # Apache-2.0
os-brick>=2.6.1 # Apache-2.0
os-resource-classes>=0.1.0 # Apache-2.0
os-resource-classes>=0.4.0 # Apache-2.0
os-traits>=0.16.0 # Apache-2.0
os-vif>=1.14.0 # Apache-2.0
os-win>=3.0.0 # Apache-2.0