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:
parent
160d1042b4
commit
b4905467db
|
@ -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
|
||||
|
|
|
@ -57,6 +57,7 @@ LOG = logging.getLogger(__name__)
|
|||
INVALID_FLAVOR_IMAGE_EXCEPTIONS = (
|
||||
exception.BadRequirementEmulatorThreadsPolicy,
|
||||
exception.CPUThreadPolicyConfigurationInvalid,
|
||||
exception.FlavorImageConflict,
|
||||
exception.ImageCPUPinningForbidden,
|
||||
exception.ImageCPUThreadPolicyForbidden,
|
||||
exception.ImageNUMATopologyAsymmetric,
|
||||
|
|
|
@ -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', {})
|
||||
|
|
|
@ -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.")
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue