scheduler: Request vTPM trait based on flavor or image
Add support for the 'hw:tpm_version' and 'hw:tpm_model' flavor extra specs along with the equivalent image metadata properties. These are picked up by the scheduler and transformed into trait requests. This is effectively a no-op for now since we don't yet have a driver that reports these traits. Part of blueprint add-emulated-virtual-tpm Change-Id: I8645c31b4ecb18afea592b2a5b360b0165626009 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
e3b0412dda
commit
5550f86623
@ -4,5 +4,5 @@
|
||||
"hw_architecture": "x86_64"
|
||||
},
|
||||
"nova_object.name": "ImageMetaPropsPayload",
|
||||
"nova_object.version": "1.4"
|
||||
"nova_object.version": "1.5"
|
||||
}
|
||||
|
@ -354,6 +354,35 @@ feature_flag_validators = [
|
||||
'description': 'The number of serial ports to allocate',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:tpm_model',
|
||||
description=(
|
||||
'The model of the attached TPM device.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'description': 'A TPM model',
|
||||
'enum': [
|
||||
'tpm-tis',
|
||||
'tpm-crb',
|
||||
],
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:tpm_version',
|
||||
description=(
|
||||
"The TPM version. Required if requesting a vTPM via the "
|
||||
"'hw:tpm_model' extra spec or equivalent image metadata property."
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'description': 'A TPM version.',
|
||||
'enum': [
|
||||
'1.2',
|
||||
'2.0',
|
||||
],
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:watchdog_action',
|
||||
description=(
|
||||
|
@ -121,7 +121,8 @@ class ImageMetaPropsPayload(base.NotificationPayloadBase):
|
||||
# Version 1.2: Added hw_pci_numa_affinity_policy field
|
||||
# Version 1.3: Added hw_mem_encryption, hw_pmu and hw_time_hpet fields
|
||||
# Version 1.4: Added 'mixed' to hw_cpu_policy field
|
||||
VERSION = '1.4'
|
||||
# Version 1.5: Added 'hw_tpm_model' and 'hw_tpm_version' fields
|
||||
VERSION = '1.5'
|
||||
|
||||
SCHEMA = {
|
||||
k: ('image_meta_props', k) for k in image_meta.ImageMetaProps.fields}
|
||||
|
@ -510,6 +510,21 @@ class RNGModel(BaseNovaEnum):
|
||||
ALL = (VIRTIO,)
|
||||
|
||||
|
||||
class TPMModel(BaseNovaEnum):
|
||||
|
||||
TIS = "tpm-tis"
|
||||
CRB = "tpm-crb"
|
||||
|
||||
ALL = (TIS, CRB)
|
||||
|
||||
|
||||
class TPMVersion(BaseNovaEnum):
|
||||
v1_2 = "1.2"
|
||||
v2_0 = "2.0"
|
||||
|
||||
ALL = (v1_2, v2_0)
|
||||
|
||||
|
||||
class SCSIModel(BaseNovaEnum):
|
||||
|
||||
BUSLOGIC = "buslogic"
|
||||
@ -1230,6 +1245,14 @@ class RNGModelField(BaseEnumField):
|
||||
AUTO_TYPE = RNGModel()
|
||||
|
||||
|
||||
class TPMModelField(BaseEnumField):
|
||||
AUTO_TYPE = TPMModel()
|
||||
|
||||
|
||||
class TPMVersionField(BaseEnumField):
|
||||
AUTO_TYPE = TPMVersion()
|
||||
|
||||
|
||||
class SCSIModelField(BaseEnumField):
|
||||
AUTO_TYPE = SCSIModel()
|
||||
|
||||
|
@ -176,14 +176,18 @@ class ImageMetaProps(base.NovaObject):
|
||||
# Version 1.24: Added 'hw_mem_encryption' field
|
||||
# Version 1.25: Added 'hw_pci_numa_affinity_policy' field
|
||||
# Version 1.26: Added 'mixed' to 'hw_cpu_policy' field
|
||||
# Version 1.27: Added 'hw_tpm_model' and 'hw_tpm_version' fields
|
||||
# NOTE(efried): When bumping this version, the version of
|
||||
# ImageMetaPropsPayload must also be bumped. See its docstring for details.
|
||||
VERSION = '1.26'
|
||||
VERSION = '1.27'
|
||||
|
||||
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, 27):
|
||||
primitive.pop('hw_tpm_model', None)
|
||||
primitive.pop('hw_tpm_version', None)
|
||||
if target_version < (1, 26):
|
||||
policy = primitive.get('hw_cpu_policy', None)
|
||||
if policy == fields.CPUAllocationPolicy.MIXED:
|
||||
@ -403,6 +407,11 @@ class ImageMetaProps(base.NovaObject):
|
||||
# boolean - If true, this will enable the virtio-multiqueue feature
|
||||
'hw_vif_multiqueue_enabled': fields.FlexibleBooleanField(),
|
||||
|
||||
# name of emulated TPM model to use.
|
||||
'hw_tpm_model': fields.TPMModelField(),
|
||||
# version of emulated TPM to use.
|
||||
'hw_tpm_version': fields.TPMVersionField(),
|
||||
|
||||
# if true download using bittorrent
|
||||
'img_bittorrent': fields.FlexibleBooleanField(),
|
||||
|
||||
|
@ -167,6 +167,8 @@ class ResourceRequest(object):
|
||||
|
||||
self._translate_vpmems_request(request_spec.flavor)
|
||||
|
||||
self._translate_vtpm_request(request_spec.flavor, image)
|
||||
|
||||
self.strip_zeros()
|
||||
|
||||
def _process_requested_resources(self, request_spec):
|
||||
@ -213,6 +215,20 @@ class ResourceRequest(object):
|
||||
# supported in image traits
|
||||
self._add_trait(None, trait, "required")
|
||||
|
||||
def _translate_vtpm_request(self, flavor, image):
|
||||
vtpm_config = hardware.get_vtpm_constraint(flavor, image)
|
||||
if not vtpm_config:
|
||||
return
|
||||
|
||||
# Require the appropriate vTPM version support trait on a host.
|
||||
if vtpm_config.version == obj_fields.TPMVersion.v1_2:
|
||||
trait = os_traits.COMPUTE_SECURITY_TPM_1_2
|
||||
else:
|
||||
trait = os_traits.COMPUTE_SECURITY_TPM_2_0
|
||||
|
||||
self._add_trait(None, trait, "required")
|
||||
LOG.debug("Requiring emulated TPM support via trait %s.", trait)
|
||||
|
||||
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
|
||||
|
@ -1249,7 +1249,8 @@ class TestInstanceNotificationSample(
|
||||
'nova_object.data': {},
|
||||
'nova_object.name': 'ImageMetaPropsPayload',
|
||||
'nova_object.namespace': 'nova',
|
||||
'nova_object.version': u'1.4'},
|
||||
'nova_object.version': u'1.5',
|
||||
},
|
||||
'image.size': 58145823,
|
||||
'image.tags': [],
|
||||
'scheduler_hints': {'_nova_check_type': ['rebuild']},
|
||||
@ -1344,7 +1345,8 @@ class TestInstanceNotificationSample(
|
||||
'nova_object.data': {},
|
||||
'nova_object.name': 'ImageMetaPropsPayload',
|
||||
'nova_object.namespace': 'nova',
|
||||
'nova_object.version': u'1.4'},
|
||||
'nova_object.version': u'1.5',
|
||||
},
|
||||
'image.size': 58145823,
|
||||
'image.tags': [],
|
||||
'scheduler_hints': {'_nova_check_type': ['rebuild']},
|
||||
|
@ -387,7 +387,7 @@ notification_object_data = {
|
||||
# ImageMetaProps, so when you see a fail here for that reason, you must
|
||||
# *also* bump the version of ImageMetaPropsPayload. See its docstring for
|
||||
# more information.
|
||||
'ImageMetaPropsPayload': '1.4-036c794843b95a3a39ee70830f5f6557',
|
||||
'ImageMetaPropsPayload': '1.5-f4074a974d4a9f77e302a53ee9340287',
|
||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f',
|
||||
'InstanceActionRebuildNotification':
|
||||
|
@ -411,3 +411,16 @@ class TestImageMetaProps(test.NoDBTestCase):
|
||||
old_primitive = obj.obj_to_primitive('1.22')
|
||||
self.assertIn('hw_pmu', primitive['nova_object.data'])
|
||||
self.assertNotIn('hw_pmu', old_primitive['nova_object.data'])
|
||||
|
||||
def test_obj_make_compatible_1_26(self):
|
||||
"""Test that checks if we pop hw_tpm_model and hw_tpm_version."""
|
||||
obj = objects.ImageMetaProps(
|
||||
hw_tpm_model='tpm-tis', hw_tpm_version='1.2',
|
||||
)
|
||||
primitive = obj.obj_to_primitive()
|
||||
self.assertIn('hw_tpm_model', primitive['nova_object.data'])
|
||||
self.assertIn('hw_tpm_version', primitive['nova_object.data'])
|
||||
|
||||
primitive = obj.obj_to_primitive('1.25')
|
||||
self.assertNotIn('hw_tpm_model', primitive['nova_object.data'])
|
||||
self.assertNotIn('hw_tpm_version', primitive['nova_object.data'])
|
||||
|
@ -1077,7 +1077,7 @@ object_data = {
|
||||
'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0',
|
||||
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
|
||||
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
|
||||
'ImageMetaProps': '1.26-b9f136cd10a2b5ffb3ae44332f2f687d',
|
||||
'ImageMetaProps': '1.27-f3f17d5e35146a0dbb56420ffc4f3990',
|
||||
'Instance': '2.7-d187aec68cad2e4d8b8a03a68e4739ce',
|
||||
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
|
||||
'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4',
|
||||
|
@ -1119,6 +1119,56 @@ class TestUtils(TestUtilsBase):
|
||||
rr = utils.ResourceRequest(rs)
|
||||
self.assertResourceRequestsEqual(expected, rr)
|
||||
|
||||
def test_resource_request_with_vtpm_1_2(self):
|
||||
flavor = objects.Flavor(
|
||||
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
||||
extra_specs={'hw:tpm_version': '1.2', 'hw:tpm_model': 'tpm-tis'},
|
||||
)
|
||||
image = objects.ImageMeta(
|
||||
properties=objects.ImageMetaProps(
|
||||
hw_tpm_version='1.2',
|
||||
hw_tpm_model='tpm-tis',
|
||||
)
|
||||
)
|
||||
expected = FakeResourceRequest()
|
||||
expected._rg_by_id[None] = objects.RequestGroup(
|
||||
use_same_provider=False,
|
||||
required_traits={'COMPUTE_SECURITY_TPM_1_2'},
|
||||
resources={
|
||||
'VCPU': 1,
|
||||
'MEMORY_MB': 1024,
|
||||
'DISK_GB': 15,
|
||||
},
|
||||
)
|
||||
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
|
||||
rr = utils.ResourceRequest(rs)
|
||||
self.assertResourceRequestsEqual(expected, rr)
|
||||
|
||||
def test_resource_request_with_vtpm_2_0(self):
|
||||
flavor = objects.Flavor(
|
||||
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
||||
extra_specs={'hw:tpm_version': '2.0', 'hw:tpm_model': 'tpm-crb'},
|
||||
)
|
||||
image = objects.ImageMeta(
|
||||
properties=objects.ImageMetaProps(
|
||||
hw_tpm_version='2.0',
|
||||
hw_tpm_model='tpm-crb',
|
||||
)
|
||||
)
|
||||
expected = FakeResourceRequest()
|
||||
expected._rg_by_id[None] = objects.RequestGroup(
|
||||
use_same_provider=False,
|
||||
required_traits={'COMPUTE_SECURITY_TPM_2_0'},
|
||||
resources={
|
||||
'VCPU': 1,
|
||||
'MEMORY_MB': 1024,
|
||||
'DISK_GB': 15,
|
||||
},
|
||||
)
|
||||
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
|
||||
rr = utils.ResourceRequest(rs)
|
||||
self.assertResourceRequestsEqual(expected, rr)
|
||||
|
||||
def test_resource_request_add_group_inserts_the_group(self):
|
||||
flavor = objects.Flavor(
|
||||
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
|
||||
|
@ -4998,6 +4998,65 @@ class PCINUMAAffinityPolicyTest(test.NoDBTestCase):
|
||||
image_meta.properties.hw_pci_numa_affinity_policy = "fake"
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VTPMConfigTest(test.NoDBTestCase):
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
# pass: no configuration
|
||||
(None, None, None, None, None),
|
||||
# pass: flavor-only (TIS) configuration
|
||||
('1.2', 'tpm-tis', None, None, hw.VTPMConfig('1.2', 'tpm-tis')),
|
||||
# pass: image-only (TIS) configuration
|
||||
(None, None, '1.2', 'tpm-tis', hw.VTPMConfig('1.2', 'tpm-tis')),
|
||||
# pass: identical image and flavor (TIS) configuration
|
||||
('1.2', 'tpm-tis', '1.2', 'tpm-tis', hw.VTPMConfig('1.2', 'tpm-tis')),
|
||||
# pass: identical image and flavor (CRB) configuration
|
||||
('2.0', 'tpm-crb', '2.0', 'tpm-crb', hw.VTPMConfig('2.0', 'tpm-crb')),
|
||||
# fail: mismatched image and flavor configuration
|
||||
('1.2', 'tpm-tis', '2.0', 'tpm-crb', exception.FlavorImageConflict),
|
||||
# fail: invalid version
|
||||
('1.3', 'tpm-tis', None, None, exception.Invalid),
|
||||
# fail: invalid model
|
||||
('1.2', 'tpm-foo', None, None, exception.Invalid),
|
||||
# fail: invalid version/model combination
|
||||
('1.2', 'tpm-crb', None, None, exception.Invalid),
|
||||
)
|
||||
def test_get_vtpm_constraint(
|
||||
self, flavor_version, flavor_model, image_version, image_model,
|
||||
expected,
|
||||
):
|
||||
extra_specs = {}
|
||||
|
||||
if flavor_version:
|
||||
extra_specs['hw:tpm_version'] = flavor_version
|
||||
|
||||
if flavor_model:
|
||||
extra_specs['hw:tpm_model'] = flavor_model
|
||||
|
||||
image_meta_props = {}
|
||||
|
||||
if image_version:
|
||||
image_meta_props['hw_tpm_version'] = image_version
|
||||
|
||||
if image_model:
|
||||
image_meta_props['hw_tpm_model'] = image_model
|
||||
|
||||
flavor = objects.Flavor(
|
||||
name='foo', vcpus=1, memory_mb=1024, extra_specs=extra_specs)
|
||||
image_meta = objects.ImageMeta.from_dict(
|
||||
{'name': 'bar', 'properties': image_meta_props})
|
||||
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(
|
||||
expected, hw.get_vtpm_constraint, flavor, image_meta,
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
expected, hw.get_vtpm_constraint(flavor, image_meta),
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class RescuePropertyTestCase(test.NoDBTestCase):
|
||||
|
||||
|
@ -1171,10 +1171,39 @@ def _get_flavor_image_meta(
|
||||
flavor_key = ':'.join(['hw', key])
|
||||
image_key = '_'.join(['hw', key])
|
||||
|
||||
flavor_policy = flavor.get('extra_specs', {}).get(flavor_key, default)
|
||||
image_policy = image_meta.properties.get(image_key, default)
|
||||
flavor_value = flavor.get('extra_specs', {}).get(flavor_key, default)
|
||||
image_value = image_meta.properties.get(image_key, default)
|
||||
|
||||
return flavor_policy, image_policy
|
||||
return flavor_value, image_value
|
||||
|
||||
|
||||
def _get_unique_flavor_image_meta(
|
||||
key: str,
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
default: ty.Any = None
|
||||
) -> ty.Any:
|
||||
"""A variant of '_get_flavor_image_meta' that errors out on conflicts."""
|
||||
flavor_value, image_value = _get_flavor_image_meta(
|
||||
key, flavor, image_meta, default,
|
||||
)
|
||||
if image_value and flavor_value and image_value != flavor_value:
|
||||
msg = _(
|
||||
"Flavor %(flavor_name)s has hw:%(key)s extra spec explicitly "
|
||||
"set to %(flavor_val)s, conflicting with image %(image_name)s "
|
||||
"which has hw_%(key)s explicitly set to %(image_val)s."
|
||||
)
|
||||
raise exception.FlavorImageConflict(
|
||||
msg % {
|
||||
'key': key,
|
||||
'flavor_name': flavor.name,
|
||||
'flavor_val': flavor_value,
|
||||
'image_name': image_meta.name,
|
||||
'image_val': image_value,
|
||||
},
|
||||
)
|
||||
|
||||
return flavor_value or image_value
|
||||
|
||||
|
||||
def get_mem_encryption_constraint(
|
||||
@ -1814,6 +1843,47 @@ def get_pci_numa_policy_constraint(flavor, image_meta):
|
||||
return policy
|
||||
|
||||
|
||||
def get_vtpm_constraint(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
) -> ty.Optional[VTPMConfig]:
|
||||
"""Validate and return the requested vTPM configuration.
|
||||
|
||||
:param flavor: ``nova.objects.Flavor`` instance
|
||||
:param image_meta: ``nova.objects.ImageMeta`` instance
|
||||
:raises: nova.exception.FlavorImageConflict if a value is specified in both
|
||||
the flavor and the image, but the values do not match
|
||||
:raises: nova.exception.Invalid if a value or combination of values is
|
||||
invalid
|
||||
:returns: A named tuple containing the vTPM version and model, else None.
|
||||
"""
|
||||
version = _get_unique_flavor_image_meta('tpm_version', flavor, image_meta)
|
||||
if version is None:
|
||||
return None
|
||||
|
||||
if version not in fields.TPMVersion.ALL:
|
||||
raise exception.Invalid(
|
||||
"Invalid TPM version %(version)r. Allowed values: %(valid)s." %
|
||||
{'version': version, 'valid': ', '.join(fields.TPMVersion.ALL)}
|
||||
)
|
||||
|
||||
model = _get_unique_flavor_image_meta('tpm_model', flavor, image_meta)
|
||||
if model is None:
|
||||
# model defaults to TIS
|
||||
model = fields.TPMModel.TIS
|
||||
elif model not in fields.TPMModel.ALL:
|
||||
raise exception.Invalid(
|
||||
"Invalid TPM model %(model)r. Allowed values: %(valid)s." %
|
||||
{'model': model, 'valid': ', '.join(fields.TPMModel.ALL)}
|
||||
)
|
||||
elif model == fields.TPMModel.CRB and version != fields.TPMVersion.v2_0:
|
||||
raise exception.Invalid(
|
||||
"TPM model CRB is only valid with TPM version 2.0."
|
||||
)
|
||||
|
||||
return VTPMConfig(version, model)
|
||||
|
||||
|
||||
def numa_get_constraints(flavor, image_meta):
|
||||
"""Return topology related to input request.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user