From c8674a88b690cab9e38c786e40f95efefe357c13 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Fri, 16 Feb 2024 15:43:07 +0900 Subject: [PATCH] libvirt: Report available TPM models Libvirt 8.0.0 introduced the new domain capabilities API filed to present available TPM models. This introduces the logic to report available TPM models as compute node traits, and use that trait for scheduling to ensure a request tpm model is available at the compute node where the instance is being launched. Depends-on: https://review.opendev.org/c/openstack/os-traits/+/909107 Implements: blueprint libvirt-detect-vtpm-support Change-Id: Iec98e7b0d19f37f094152a61a26790fcdf3328d9 --- nova/scheduler/utils.py | 20 +++++++---- nova/tests/fixtures/libvirt.py | 1 - nova/tests/unit/scheduler/test_utils.py | 37 +++++++++++++++++++-- nova/tests/unit/virt/libvirt/test_driver.py | 16 ++++++--- nova/tests/unit/virt/libvirt/test_host.py | 2 +- nova/virt/libvirt/driver.py | 34 +++++++++++++++---- nova/virt/libvirt/host.py | 18 ++++++++++ requirements.txt | 2 +- 8 files changed, 107 insertions(+), 23 deletions(-) diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index 961ef93e3060..daeb2638e09a 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -295,14 +295,20 @@ class ResourceRequest(object): 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 + # Require the appropriate vTPM model support trait on a host. + model_trait = os_traits.COMPUTE_SECURITY_TPM_TIS + if vtpm_config.model == obj_fields.TPMModel.CRB: + model_trait = os_traits.COMPUTE_SECURITY_TPM_CRB - self._add_trait(trait, 'required') - LOG.debug("Requiring emulated TPM support via trait %s.", trait) + # Require the appropriate vTPM version support trait on a host. + version_trait = os_traits.COMPUTE_SECURITY_TPM_1_2 + if vtpm_config.version == obj_fields.TPMVersion.v2_0: + version_trait = os_traits.COMPUTE_SECURITY_TPM_2_0 + + self._add_trait(model_trait, 'required') + self._add_trait(version_trait, 'required') + LOG.debug("Requiring emulated TPM support via trait %s and %s.", + version_trait, model_trait) def _translate_memory_encryption(self, flavor, image): """When the hw:mem_encryption extra spec or the hw_mem_encryption diff --git a/nova/tests/fixtures/libvirt.py b/nova/tests/fixtures/libvirt.py index 35a11547cf40..3784f1005c91 100644 --- a/nova/tests/fixtures/libvirt.py +++ b/nova/tests/fixtures/libvirt.py @@ -2122,7 +2122,6 @@ class Connection(object): tpm-tis - tpm-crb passthrough diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py index d7382228e889..41a6445b9991 100644 --- a/nova/tests/unit/scheduler/test_utils.py +++ b/nova/tests/unit/scheduler/test_utils.py @@ -1308,6 +1308,33 @@ class TestUtils(TestUtilsBase): rr = utils.ResourceRequest.from_request_spec(rs) self.assertResourceRequestsEqual(expected, rr) + def test_resource_request_from_request_spec_with_vtpm_version_only(self): + flavor = objects.Flavor( + vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0, + extra_specs={'hw:tpm_version': '1.2'}, + ) + image = objects.ImageMeta( + properties=objects.ImageMetaProps( + hw_tpm_version='1.2', + ) + ) + expected = FakeResourceRequest() + expected._rg_by_id[None] = objects.RequestGroup( + use_same_provider=False, + required_traits={ + 'COMPUTE_SECURITY_TPM_1_2', + 'COMPUTE_SECURITY_TPM_TIS', + }, + resources={ + 'VCPU': 1, + 'MEMORY_MB': 1024, + 'DISK_GB': 15, + }, + ) + rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False) + rr = utils.ResourceRequest.from_request_spec(rs) + self.assertResourceRequestsEqual(expected, rr) + def test_resource_request_from_request_spec_with_vtpm_1_2(self): flavor = objects.Flavor( vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0, @@ -1322,7 +1349,10 @@ class TestUtils(TestUtilsBase): expected = FakeResourceRequest() expected._rg_by_id[None] = objects.RequestGroup( use_same_provider=False, - required_traits={'COMPUTE_SECURITY_TPM_1_2'}, + required_traits={ + 'COMPUTE_SECURITY_TPM_1_2', + 'COMPUTE_SECURITY_TPM_TIS', + }, resources={ 'VCPU': 1, 'MEMORY_MB': 1024, @@ -1347,7 +1377,10 @@ class TestUtils(TestUtilsBase): expected = FakeResourceRequest() expected._rg_by_id[None] = objects.RequestGroup( use_same_provider=False, - required_traits={'COMPUTE_SECURITY_TPM_2_0'}, + required_traits={ + 'COMPUTE_SECURITY_TPM_2_0', + 'COMPUTE_SECURITY_TPM_CRB', + }, resources={ 'VCPU': 1, 'MEMORY_MB': 1024, diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 8046a4f7efda..c91c01fe60a2 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -997,6 +997,8 @@ class LibvirtConnTestCase(test.NoDBTestCase, 'COMPUTE_NET_VIRTIO_PACKED': True, 'COMPUTE_SECURITY_TPM_1_2': False, 'COMPUTE_SECURITY_TPM_2_0': False, + 'COMPUTE_SECURITY_TPM_TIS': False, + 'COMPUTE_SECURITY_TPM_CRB': False, 'COMPUTE_STORAGE_BUS_VIRTIO': True, 'COMPUTE_VIOMMU_MODEL_AUTO': True, 'COMPUTE_VIOMMU_MODEL_INTEL': True, @@ -1049,6 +1051,8 @@ class LibvirtConnTestCase(test.NoDBTestCase, 'COMPUTE_NET_VIRTIO_PACKED': True, 'COMPUTE_SECURITY_TPM_1_2': False, 'COMPUTE_SECURITY_TPM_2_0': False, + 'COMPUTE_SECURITY_TPM_TIS': False, + 'COMPUTE_SECURITY_TPM_CRB': False, 'COMPUTE_VIOMMU_MODEL_AUTO': True, 'COMPUTE_VIOMMU_MODEL_INTEL': True, 'COMPUTE_VIOMMU_MODEL_SMMUV3': True, @@ -22506,7 +22510,9 @@ class TestUpdateProviderTree(test.NoDBTestCase): def test_update_provider_tree_with_tpm_traits(self): self.flags(swtpm_enabled=True, group='libvirt') self._test_update_provider_tree() - for trait in ('COMPUTE_SECURITY_TPM_2_0', 'COMPUTE_SECURITY_TPM_1_2'): + for trait in ( + 'COMPUTE_SECURITY_TPM_TIS', 'COMPUTE_SECURITY_TPM_CRB', + 'COMPUTE_SECURITY_TPM_2_0', 'COMPUTE_SECURITY_TPM_1_2'): self.assertIn(trait, self.pt.data(self.cn_rp['uuid']).traits) @mock.patch.object( @@ -22516,7 +22522,9 @@ class TestUpdateProviderTree(test.NoDBTestCase): def test_update_provider_tree_with_tpm_traits_supported(self): self.flags(swtpm_enabled=True, group='libvirt') self._test_update_provider_tree() - for trait in ('COMPUTE_SECURITY_TPM_2_0', 'COMPUTE_SECURITY_TPM_1_2'): + for trait in ( + 'COMPUTE_SECURITY_TPM_TIS', 'COMPUTE_SECURITY_TPM_CRB', + 'COMPUTE_SECURITY_TPM_2_0', 'COMPUTE_SECURITY_TPM_1_2'): self.assertIn(trait, self.pt.data(self.cn_rp['uuid']).traits) @mock.patch.object( @@ -22527,9 +22535,9 @@ class TestUpdateProviderTree(test.NoDBTestCase): def test_update_provider_tree_with_tpm_traits_versions(self): self.flags(swtpm_enabled=True, group='libvirt') self._test_update_provider_tree() - for trait in ('COMPUTE_SECURITY_TPM_2_0',): + for trait in ('COMPUTE_SECURITY_TPM_TIS', 'COMPUTE_SECURITY_TPM_2_0'): self.assertIn(trait, self.pt.data(self.cn_rp['uuid']).traits) - for trait in ('COMPUTE_SECURITY_TPM_1_2',): + for trait in ('COMPUTE_SECURITY_TPM_CRB', 'COMPUTE_SECURITY_TPM_1_2',): self.assertNotIn(trait, self.pt.data(self.cn_rp['uuid']).traits) @mock.patch('nova.virt.libvirt.driver.LibvirtDriver.' diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py index 0bfc6103cbc8..00ac58aca1d2 100644 --- a/nova/tests/unit/virt/libvirt/test_host.py +++ b/nova/tests/unit/virt/libvirt/test_host.py @@ -920,7 +920,7 @@ class HostTestCase(test.NoDBTestCase): type(caps.devices.tpm)) self.assertTrue(caps.devices.tpm.supported) self.assertEqual( - ['tpm-tis', 'tpm-crb'], + ['tpm-tis'], caps.devices.tpm.models ) self.assertEqual( diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index e90c6284544e..78ed7c9ed8e3 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -12902,23 +12902,43 @@ class LibvirtDriver(driver.ComputeDriver): return { ot.COMPUTE_SECURITY_TPM_2_0: False, ot.COMPUTE_SECURITY_TPM_1_2: False, + ot.COMPUTE_SECURITY_TPM_TIS: False, + ot.COMPUTE_SECURITY_TPM_CRB: False, } + tpm_models = self._host.tpm_models tpm_versions = self._host.tpm_versions # libvirt < 8.6 does not provide supported versions in domain # capabilities - # TODO(tkajinam): Remove this once libvirt>=8.6.0 is required. + tr = {} + if tpm_models is None: + # TODO(tkajinam): Remove this fallback once libvirt>=8.0.0 is + # required. + tr.update({ + ot.COMPUTE_SECURITY_TPM_TIS: True, + ot.COMPUTE_SECURITY_TPM_CRB: True, + }) + else: + tr.update({ + ot.COMPUTE_SECURITY_TPM_TIS: 'tpm-tis' in tpm_models, + ot.COMPUTE_SECURITY_TPM_CRB: 'tpm-crb' in tpm_models, + }) + if tpm_versions is None: - return { + # TODO(tkajinam): Remove this fallback once libvirt>=8.6.0 is + # required. + tr.update({ ot.COMPUTE_SECURITY_TPM_2_0: True, ot.COMPUTE_SECURITY_TPM_1_2: True, - } + }) + else: + tr.update({ + ot.COMPUTE_SECURITY_TPM_2_0: '2.0' in tpm_versions, + ot.COMPUTE_SECURITY_TPM_1_2: '1.2' in tpm_versions, + }) - return { - ot.COMPUTE_SECURITY_TPM_2_0: '2.0' in tpm_versions, - ot.COMPUTE_SECURITY_TPM_1_2: '1.2' in tpm_versions, - } + return tr def _get_vif_model_traits(self) -> ty.Dict[str, bool]: """Get vif model traits based on the currently enabled virt_type. diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py index e8d066c4ec49..50ff62fa133b 100644 --- a/nova/virt/libvirt/host.py +++ b/nova/virt/libvirt/host.py @@ -1884,6 +1884,24 @@ class Host(object): # safe guard return [] + @property + def tpm_models(self) -> ty.Optional[ty.List[str]]: + # we only check the host architecture and the first machine type + # because vtpm support is independent from cpu architecture + arch = self.get_capabilities().host.cpu.arch + domain_caps = self.get_domain_capabilities() + for machine_type in domain_caps[arch]: + _tpm = domain_caps[arch][machine_type].devices.tpm + # TODO(tkajinam): Remove first check once libvirt >= 8.0.0 is + # required + # TODO(tkajinam): Remove second check once libvirt >= 8.6.0 is + # required + if _tpm is None or _tpm.models is None: + return None + return _tpm.models + # safe guard + return [] + def _kernel_supports_amd_sev(self) -> bool: if not os.path.exists(SEV_KERNEL_PARAM_FILE): LOG.debug("%s does not exist", SEV_KERNEL_PARAM_FILE) diff --git a/requirements.txt b/requirements.txt index eb158af1c067..430e6e96d138 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,7 +52,7 @@ psutil>=3.2.2 # BSD oslo.versionedobjects>=1.35.0 # Apache-2.0 os-brick>=5.2 # Apache-2.0 os-resource-classes>=1.1.0 # Apache-2.0 -os-traits>=3.0.0 # Apache-2.0 +os-traits>=3.1.0 # Apache-2.0 os-vif>=3.1.0 # Apache-2.0 castellan>=0.16.0 # Apache-2.0 microversion-parse>=0.2.1 # Apache-2.0