From 87385d24115a1c5077c54a0333cb7f580a304ff0 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Fri, 29 Aug 2025 12:28:12 +0900 Subject: [PATCH] Follow-up of AMD SEV-ES support Address a few improvements we agreed to cover in follow-ups. Also fix a few problems detected during the code update. - Fix SEV-ES rp not purged when SEV and SEV-ES are disabled at the same time. The previous logic requires 2 cycles which is not necessary. - Fix the lack of NOKS policy in SEV-ES. Change-Id: I59866d39fcc6720e338c6736dffab4fd56b853da Signed-off-by: Takashi Kajinami --- .../libvirt/test_report_cpu_traits.py | 148 ++++++++- nova/tests/functional/libvirt/test_reshape.py | 9 +- nova/tests/unit/scheduler/test_utils.py | 293 +++++++++--------- nova/tests/unit/virt/libvirt/test_config.py | 4 +- nova/tests/unit/virt/libvirt/test_driver.py | 2 +- nova/virt/libvirt/config.py | 20 +- nova/virt/libvirt/driver.py | 25 +- 7 files changed, 332 insertions(+), 169 deletions(-) diff --git a/nova/tests/functional/libvirt/test_report_cpu_traits.py b/nova/tests/functional/libvirt/test_report_cpu_traits.py index 0f023eec66..bfc42f0df6 100644 --- a/nova/tests/functional/libvirt/test_report_cpu_traits.py +++ b/nova/tests/functional/libvirt/test_report_cpu_traits.py @@ -330,10 +330,12 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase): def setUp(self): super(LibvirtReportSevTraitsTests, self).setUp() + + def _init_compute(self, sev, sev_es): sev_features = (fakelibvirt.virConnect. _domain_capability_features_with_SEV_max_guests) with test.nested( - self._patch_sev_exists(True, True), + self._patch_sev_exists(sev, sev_es), self._patch_sev_open(), mock.patch.object(fakelibvirt.virConnect, '_domain_capability_features', @@ -346,20 +348,106 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase): self.start_compute() def test_sev_trait_on_off(self): - """Test that the compute service reports the SEV/SEV-ES trait in + """Test that the compute service reports the SEV trait in the list of global traits, and immediately registers it on the compute - host resource provider in the placement API, due to the SEV/SEV-ES + host resource provider in the placement API, due to the SEV capability being (mocked as) present. - Then test that if the SEV/SEV-ES capability disappears (again via + Then test that if the SEV capability disappears (again via mocking), + after a restart of the compute service, the trait gets removed from + the compute host. + + Also test that on both occasions, the inventory of the + MEM_ENCRYPTION_CONTEXT resource class on the compute host + corresponds to the absence or presence of the SEV capability. + """ + self._init_compute(True, False) + + # Make sure that SEV is enabled but SEV-ES is not enabled + self.assertTrue(self.compute.driver._host.supports_amd_sev) + self.assertFalse(self.compute.driver._host.supports_amd_sev_es) + + global_traits = self._get_all_traits() + self.assertIn(ost.HW_CPU_X86_AMD_SEV, global_traits) + self.assertIn(ost.HW_CPU_X86_AMD_SEV_ES, global_traits) + + # sev capabilities are managed by sub rp and are not present in root rp + traits = self._get_provider_traits(self.host_uuid) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV, traits) + self.assertMemEncryptionSlotsEqual(self.host_uuid, 0) + + sev_rps = self._get_amd_sev_rps() + + self.assertEqual(1, len(sev_rps['sev'])) + sev_rp_uuid = sev_rps['sev'][0]['uuid'] + sev_rp_traits = self._get_provider_traits(sev_rp_uuid) + self.assertIn(ost.HW_CPU_X86_AMD_SEV, sev_rp_traits) + self.assertMemEncryptionSlotsEqual(sev_rp_uuid, 100) + + self.assertEqual(0, len(sev_rps['sev-es'])) + + # Now simulate the host losing SEV functionality. Here we + # simulate a kernel downgrade or reconfiguration which causes + # the kvm-amd kernel module's "sev-" parameter to become + # unavailable. + sev_features = (fakelibvirt.virConnect. + _domain_capability_features_with_SEV) + with test.nested( + self._patch_sev_exists(False, False), + self._patch_sev_open(), + mock.patch.object(fakelibvirt.virConnect, + '_domain_capability_features', + new=sev_features) + ) as (mock_exists, mock_open, mock_features): + # Retrigger the detection code. In the real world this + # would be a restart of the compute service. + self.compute.driver._host._domain_caps = None + self.compute.driver._host._supports_amd_sev = None + self.compute.driver._host._supports_amd_sev_es = None + self.assertFalse(self.compute.driver._host.supports_amd_sev) + self.assertFalse(self.compute.driver._host.supports_amd_sev_es) + + mock_exists.assert_has_calls([ + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev'), + ]) + + # However it won't disappear in the provider tree and get synced + # back to placement until we force a reinventory: + self.compute.manager.reset() + # reset cached traits so they are recalculated. + self.compute.driver._static_traits = None + self._run_periodics() + + # Sanity check that we've still got the trait globally. + global_traits = self._get_all_traits() + self.assertIn(ost.HW_CPU_X86_AMD_SEV, global_traits) + self.assertIn(ost.HW_CPU_X86_AMD_SEV_ES, global_traits) + + traits = self._get_provider_traits(self.host_uuid) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV_ES, traits) + + sev_rps = self._get_amd_sev_rps() + self.assertEqual(0, len(sev_rps['sev'])) + self.assertEqual(0, len(sev_rps['sev-es'])) + + def test_sev_es_trait_on_off(self): + """Test that the compute service reports the SEV-ES trait in + the list of global traits, and immediately registers it on the compute + host resource provider in the placement API, due to the SEV-ES + capability being (mocked as) present. + + Then test that if the SEV-ES capability disappears (again via mocking), after a restart of the compute service, the trait gets removed from the compute host. Also test that on both occasions, the inventory of the MEM_ENCRYPTION_CONTEXT resource class on the compute host - corresponds to the absence or presence of the SEV/SEV-ES capability. + corresponds to the absence or presence of the SEV-ES capability. """ + self._init_compute(True, True) + # Make sure that both SEV and SEV-ES are enabled self.assertTrue(self.compute.driver._host.supports_amd_sev) self.assertTrue(self.compute.driver._host.supports_amd_sev_es) @@ -437,10 +525,54 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase): self.assertEqual(1, len(sev_rps['sev'])) self.assertEqual(0, len(sev_rps['sev-es'])) - # Now simulate the host losing SEV functionality. Here we + def test_sev_all_trait_on_off(self): + """Test that the compute service reports the SEV/SEV-ES trait in + the list of global traits, and immediately registers it on the compute + host resource provider in the placement API, due to the SEV/SEV-ES + capability being (mocked as) present. + + Then test that if the SEV/SEV-ES capability disappears (again via + mocking), after a restart of the compute service, the trait + gets removed from the compute host. + + Also test that on both occasions, the inventory of the + MEM_ENCRYPTION_CONTEXT resource class on the compute host + corresponds to the absence or presence of the SEV/SEV-ES capability. + """ + self._init_compute(True, True) + + # Make sure that both SEV and SEV-ES are enabled + self.assertTrue(self.compute.driver._host.supports_amd_sev) + self.assertTrue(self.compute.driver._host.supports_amd_sev_es) + global_traits = self._get_all_traits() + self.assertIn(ost.HW_CPU_X86_AMD_SEV, global_traits) + self.assertIn(ost.HW_CPU_X86_AMD_SEV_ES, global_traits) + + # sev capabilities are managed by sub rp and are not present in root rp + traits = self._get_provider_traits(self.host_uuid) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV, traits) + self.assertMemEncryptionSlotsEqual(self.host_uuid, 0) + + sev_rps = self._get_amd_sev_rps() + + self.assertEqual(1, len(sev_rps['sev'])) + sev_rp_uuid = sev_rps['sev'][0]['uuid'] + sev_rp_traits = self._get_provider_traits(sev_rp_uuid) + self.assertIn(ost.HW_CPU_X86_AMD_SEV, sev_rp_traits) + self.assertMemEncryptionSlotsEqual(sev_rp_uuid, 100) + + self.assertEqual(1, len(sev_rps['sev-es'])) + sev_es_rp_uuid = sev_rps['sev-es'][0]['uuid'] + sev_es_rp_traits = self._get_provider_traits(sev_es_rp_uuid) + self.assertIn(ost.HW_CPU_X86_AMD_SEV_ES, sev_es_rp_traits) + self.assertMemEncryptionSlotsEqual(sev_es_rp_uuid, 15) + self.assertEqual(1, len(sev_rps['sev'])) + + # Now simulate the host losing SEV/SEV-ES functionality. Here we # simulate a kernel downgrade or reconfiguration which causes - # the kvm-amd kernel module's "sev-" parameter to become - # unavailable. + # the kvm-amd kernel module's "sev" parameter and "sev-es" parameter + # to become unavailable, however it could also happen via a libvirt + # downgrade, for instance. sev_features = (fakelibvirt.virConnect. _domain_capability_features_with_SEV) with test.nested( diff --git a/nova/tests/functional/libvirt/test_reshape.py b/nova/tests/functional/libvirt/test_reshape.py index 03876a6886..c9f836c015 100644 --- a/nova/tests/functional/libvirt/test_reshape.py +++ b/nova/tests/functional/libvirt/test_reshape.py @@ -280,6 +280,7 @@ class SevResphapeTests(base.ServersTestBase): compute_rp_uuid = self._get_provider_uuid_by_name('compute1') inventories = self.placement.get( '/resource_providers/%s/inventories' % compute_rp_uuid).body + # MEM_ENCRYPTION_CONTEXT inventory was added to compute RP inventories['inventories']['MEM_ENCRYPTION_CONTEXT'] = { 'allocation_ratio': 1.0, 'max_unit': 1, @@ -290,6 +291,7 @@ class SevResphapeTests(base.ServersTestBase): self.placement.put( '/resource_providers/%s/inventories' % compute_rp_uuid, inventories) + # SEV trait was also added to compute RP traits = self._get_provider_traits(compute_rp_uuid) traits.append(os_traits.HW_CPU_X86_AMD_SEV) self._set_provider_traits(compute_rp_uuid, traits) @@ -322,13 +324,14 @@ class SevResphapeTests(base.ServersTestBase): self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_inventories) compute_usages = self._get_provider_usages(compute_rp_uuid) self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_usages) - + # MEM_ENCRYPTION_CONTEXT inventory/usage should be moreved to child RP sev_rp_uuid = self._get_provider_uuid_by_name('compute1_amd_sev') sev_inventories = self._get_provider_inventory(sev_rp_uuid) self.assertEqual( 16, sev_inventories['MEM_ENCRYPTION_CONTEXT']['total']) sev_usages = self._get_provider_usages(sev_rp_uuid) self.assertEqual(1, sev_usages['MEM_ENCRYPTION_CONTEXT']) + # SEV trait should be also moved to child RP sev_traits = self._get_provider_traits(sev_rp_uuid) self.assertIn(os_traits.HW_CPU_X86_AMD_SEV, sev_traits) @@ -370,6 +373,7 @@ class SevResphapeTests(base.ServersTestBase): compute_rp_uuid = self._get_provider_uuid_by_name(name) inventories = self.placement.get( '/resource_providers/%s/inventories' % compute_rp_uuid).body + # MEM_ENCRYPTION_CONTEXT inventory was added to compute RP inventories['inventories']['MEM_ENCRYPTION_CONTEXT'] = { 'allocation_ratio': 1.0, 'max_unit': 1, @@ -380,6 +384,7 @@ class SevResphapeTests(base.ServersTestBase): self.placement.put( '/resource_providers/%s/inventories' % compute_rp_uuid, inventories) + # SEV trait was also added to compute root RP traits = self._get_provider_traits(compute_rp_uuid) traits.append(os_traits.HW_CPU_X86_AMD_SEV) self._set_provider_traits(compute_rp_uuid, traits) @@ -444,7 +449,7 @@ class SevResphapeTests(base.ServersTestBase): sev_usages = self._get_provider_usages(sev_rp_uuid) self.assertEqual(1, sev_usages['MEM_ENCRYPTION_CONTEXT']) - # server2 should allocate M_E_C from compute RP + # server2 should allocate M_E_C from compute root RP compute_rp_uuid = self._get_provider_uuid_by_name('compute2') compute_usages = self._get_provider_usages(compute_rp_uuid) self.assertEqual(1, compute_usages['MEM_ENCRYPTION_CONTEXT']) diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py index fdad6a820c..3c095b11c3 100644 --- a/nova/tests/unit/scheduler/test_utils.py +++ b/nova/tests/unit/scheduler/test_utils.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import itertools from unittest import mock import ddt @@ -2301,6 +2302,7 @@ class TestUtils(TestUtilsBase): mock_map.assert_called_once_with(allocation, {uuids.rp_uuid: traits}) +@ddt.ddt class TestEncryptedMemoryTranslation(TestUtilsBase): flavor_name = 'm1.test' image_name = 'cirros' @@ -2333,13 +2335,13 @@ class TestEncryptedMemoryTranslation(TestUtilsBase): 'MEMORY_MB': 1024, 'DISK_GB': 15, } - required_traits = [] + required_traits = set() if mem_encryption_model == 'amd-sev': expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1 - required_traits = ['HW_CPU_X86_AMD_SEV'] + required_traits |= {'HW_CPU_X86_AMD_SEV'} elif mem_encryption_model == 'amd-sev-es': expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1 - required_traits = ['HW_CPU_X86_AMD_SEV_ES'] + required_traits |= {'HW_CPU_X86_AMD_SEV_ES'} elif mem_encryption_model is not None: self.fail('invalid mem_encryption_model: %s' % mem_encryption_model) @@ -2348,7 +2350,7 @@ class TestEncryptedMemoryTranslation(TestUtilsBase): expected._rg_by_id[None] = objects.RequestGroup( use_same_provider=False, resources=expected_resources, - required_traits=set(required_traits) + required_traits=required_traits ) return expected @@ -2362,34 +2364,35 @@ class TestEncryptedMemoryTranslation(TestUtilsBase): 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}) + @ddt.data('0', 'false', 'False') + def test_encrypted_memory_support_false_extra_spec(self, extra_spec): + 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)) - ) + @ddt.data('0', 'false', 'False') + def test_encrypted_memory_support_false_image_prop(self, image_prop): + 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)) - ) + @ddt.unpack + @ddt.data(*itertools.product( + ('0', 'false', 'False'), ('0', 'false', 'False'))) + def test_encrypted_memory_support_both_false(self, image_prop, extra_spec): + 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, @@ -2432,19 +2435,25 @@ class TestEncryptedMemoryTranslation(TestUtilsBase): } 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 - ) + @ddt.unpack + @ddt.data(*itertools.product( + ('1', 'true', 'True'), + ('0', 'false', 'False'))) + def test_encrypted_memory_support_conflict1( + self, image_prop_in, extra_spec): + 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 - ) + @ddt.unpack + @ddt.data(*itertools.product( + ('0', 'false', 'False'), + ('1', 'true', 'True'))) + def test_encrypted_memory_support_conflict2( + self, image_prop_in, extra_spec): + 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, @@ -2467,120 +2476,122 @@ class TestEncryptedMemoryTranslation(TestUtilsBase): model, me_trait) ]) - 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( - id='005249be-3c2f-4351-9df7-29bb13c21b14', - properties=objects.ImageMetaProps( - hw_machine_type='q35', - hw_firmware_type='uefi')) - ) + @ddt.data('1', 'true', 'True') + def test_encrypted_memory_support_extra_spec(self, extra_spec): + self._test_encrypted_memory_support_required( + 'hw:mem_encryption extra spec', + {'hw:mem_encryption': extra_spec}, + image=objects.ImageMeta( + id='005249be-3c2f-4351-9df7-29bb13c21b14', + properties=objects.ImageMetaProps( + hw_machine_type='q35', + 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( - id='005249be-3c2f-4351-9df7-29bb13c21b14', - name=self.image_name, - properties=objects.ImageMetaProps( - hw_machine_type='q35', - hw_firmware_type='uefi', - hw_mem_encryption=image_prop)) - ) + @ddt.data('1', 'true', 'True') + def test_encrypted_memory_support_image_prop(self, image_prop): + self._test_encrypted_memory_support_required( + 'hw_mem_encryption image property', + {}, + image=objects.ImageMeta( + id='005249be-3c2f-4351-9df7-29bb13c21b14', + name=self.image_name, + properties=objects.ImageMetaProps( + hw_machine_type='q35', + 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( - id='005249be-3c2f-4351-9df7-29bb13c21b14', - name=self.image_name, - properties=objects.ImageMetaProps( - hw_machine_type='q35', - hw_firmware_type='uefi', - hw_mem_encryption=image_prop)) - ) + @ddt.unpack + @ddt.data(*itertools.product( + ('1', 'true', 'True'), ('1', 'true', 'True'))) + def test_encrypted_memory_support_both_required( + self, image_prop, extra_spec): + 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( + id='005249be-3c2f-4351-9df7-29bb13c21b14', + name=self.image_name, + properties=objects.ImageMetaProps( + hw_machine_type='q35', + hw_firmware_type='uefi', + hw_mem_encryption=image_prop)) + ) - def test_encrypted_memory_model_extra_spec(self): - for model in ('amd-sev', 'amd-sev-es'): - self._test_encrypted_memory_support_required( - 'hw:mem_encryption extra spec', - {'hw:mem_encryption': 'true', - 'hw:mem_encryption_model': model}, - image=objects.ImageMeta( - id='005249be-3c2f-4351-9df7-29bb13c21b14', - properties=objects.ImageMetaProps( - hw_machine_type='q35', - hw_firmware_type='uefi')), - model=model - ) + @ddt.data('amd-sev', 'amd-sev-es') + def test_encrypted_memory_model_extra_spec(self, model): + self._test_encrypted_memory_support_required( + 'hw:mem_encryption extra spec', + {'hw:mem_encryption': 'true', + 'hw:mem_encryption_model': model}, + image=objects.ImageMeta( + id='005249be-3c2f-4351-9df7-29bb13c21b14', + properties=objects.ImageMetaProps( + hw_machine_type='q35', + hw_firmware_type='uefi')), + model=model + ) - def test_encrypted_memory_model_image_prop(self): - for model in ('amd-sev', 'amd-sev-es'): - self._test_encrypted_memory_support_required( - 'hw_mem_encryption image property', - {}, - image=objects.ImageMeta( - id='005249be-3c2f-4351-9df7-29bb13c21b14', - name=self.image_name, - properties=objects.ImageMetaProps( - hw_machine_type='q35', - hw_firmware_type='uefi', - hw_mem_encryption='true', - hw_mem_encryption_model=model)), - model=model - ) - - def test_encrypted_memory_model_both_required(self): - for model in ('amd-sev', 'amd-sev-es'): - self._test_encrypted_memory_support_required( - 'hw:mem_encryption extra spec and ' - 'hw_mem_encryption image property', - {'hw:mem_encryption': 'true', - 'hw:mem_encryption_model': model}, - image=objects.ImageMeta( - id='005249be-3c2f-4351-9df7-29bb13c21b14', - name=self.image_name, - properties=objects.ImageMetaProps( - hw_machine_type='q35', - hw_firmware_type='uefi', - hw_mem_encryption='true', - hw_mem_encryption_model=model)), - model=model - ) - - def test_encrypted_memory_model_conflict_1(self): - for f_model, i_model in ( - ('amd-sev', 'amd-sev-es'), - ('amd-sev-es', 'amd-sev') - ): - image = objects.ImageMeta( + @ddt.data('amd-sev', 'amd-sev-es') + def test_encrypted_memory_model_image_prop(self, model): + self._test_encrypted_memory_support_required( + 'hw_mem_encryption image property', + {}, + image=objects.ImageMeta( + id='005249be-3c2f-4351-9df7-29bb13c21b14', name=self.image_name, properties=objects.ImageMetaProps( hw_machine_type='q35', hw_firmware_type='uefi', hw_mem_encryption='true', - hw_mem_encryption_model=i_model - ) - ) - reqspec = self._get_request_spec( - extra_specs={ - 'hw:mem_encryption': 'true', - 'hw:mem_encryption_model': f_model, - }, - image=image) - self.assertRaises( - exception.FlavorImageConflict, - utils.ResourceRequest.from_request_spec, reqspec + hw_mem_encryption_model=model)), + model=model + ) + + @ddt.data('amd-sev', 'amd-sev-es') + def test_encrypted_memory_model_both_required(self, model): + self._test_encrypted_memory_support_required( + 'hw:mem_encryption extra spec and ' + 'hw_mem_encryption image property', + {'hw:mem_encryption': 'true', + 'hw:mem_encryption_model': model}, + image=objects.ImageMeta( + id='005249be-3c2f-4351-9df7-29bb13c21b14', + name=self.image_name, + properties=objects.ImageMetaProps( + hw_machine_type='q35', + hw_firmware_type='uefi', + hw_mem_encryption='true', + hw_mem_encryption_model=model)), + model=model + ) + + @ddt.unpack + @ddt.data( + ('amd-sev', 'amd-sev-es'), + ('amd-sev-es', 'amd-sev')) + def test_encrypted_memory_model_conflict_1(self, f_model, i_model): + image = objects.ImageMeta( + name=self.image_name, + properties=objects.ImageMetaProps( + hw_machine_type='q35', + hw_firmware_type='uefi', + hw_mem_encryption='true', + hw_mem_encryption_model=i_model ) + ) + reqspec = self._get_request_spec( + extra_specs={ + 'hw:mem_encryption': 'true', + 'hw:mem_encryption_model': f_model, + }, + image=image) + self.assertRaises( + exception.FlavorImageConflict, + utils.ResourceRequest.from_request_spec, reqspec + ) class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase): diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py index 1f3f5372ac..ae3017261d 100644 --- a/nova/tests/unit/virt/libvirt/test_config.py +++ b/nova/tests/unit/virt/libvirt/test_config.py @@ -2716,11 +2716,11 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest): self.assertXmlEqual(launch_security_expected, xml) - obj.policy = 0x0035 + obj.policy = obj.DEFAULT_SEV_ES_POLICY xml = obj.to_xml() launch_security_expected = """ - 0x0035 + 0x0037 47 1 """ diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index fd4a3e1813..ec5fe68b64 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -3974,7 +3974,7 @@ class LibvirtConnTestCase(test.NoDBTestCase, @ddt.data( {'sev_model': None, 'sev_policy': 0x0033}, {'sev_model': 'amd-sev', 'sev_policy': 0x0033}, - {'sev_model': 'amd-sev-es', 'sev_policy': 0x0035} + {'sev_model': 'amd-sev-es', 'sev_policy': 0x0037} ) @mock.patch.object(host.Host, 'get_domain_capabilities') @mock.patch.object(designer, 'set_driver_iommu_for_all_devices') diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py index 512011cee8..43bbdbceca 100644 --- a/nova/virt/libvirt/config.py +++ b/nova/virt/libvirt/config.py @@ -3018,13 +3018,27 @@ class LibvirtConfigGuestFeatureHyperV(LibvirtConfigGuestFeature): class LibvirtConfigGuestSEVLaunchSecurity(LibvirtConfigObject): + # NOTE(tkajinam): See also + # https://gitlab.com/qemu-project/qemu/-/blob/v10.1.0/target/i386/sev.h + SEV_POLICY_NODEBG = 0x1 + SEV_POLICY_NOKS = 0x2 + SEV_POLICY_ES = 0x4 + SEV_POLICY_NOSEND = 0x8 + SEV_POLICY_DOMAIN = 0x10 + SEV_POLICY_SEV = 0x20 + + DEFAULT_SEV_POLICY = ( + SEV_POLICY_NODEBG | SEV_POLICY_NOKS | + SEV_POLICY_DOMAIN | SEV_POLICY_SEV) + DEFAULT_SEV_ES_POLICY = ( + DEFAULT_SEV_POLICY | SEV_POLICY_ES) def __init__(self, **kwargs): super(LibvirtConfigGuestSEVLaunchSecurity, self).__init__( root_name='launchSecurity', **kwargs) - - # hardcoded default for SEV according to the spec - self.policy = 0x0033 + # Use SEV policy as default, because SEV is the default encryption + # model + self.policy = self.DEFAULT_SEV_POLICY self.cbitpos = None self.reduced_phys_bits = None diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 355624ffae..4c7cd102be 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -7702,8 +7702,9 @@ class LibvirtDriver(driver.ComputeDriver): launch_security = vconfig.LibvirtConfigGuestSEVLaunchSecurity() launch_security.cbitpos = sev.cbitpos launch_security.reduced_phys_bits = sev.reduced_phys_bits + # NOTE(tkajinam): Default policy is for SEV if model == fields.MemEncryptionModel.AMD_SEV_ES: - launch_security.policy = 0x0035 + launch_security.policy = launch_security.DEFAULT_SEV_ES_POLICY guest.launch_security = launch_security def _find_sev_feature(self, arch, mach_type): @@ -9560,8 +9561,8 @@ class LibvirtDriver(driver.ComputeDriver): metadata=vpmem) resources[rc].add(resource_obj) - def _update_provider_tree_for_memory_encryption(self, provider_tree, - nodename, allocations): + def _update_provider_tree_for_memory_encryption( + self, provider_tree, nodename, allocations): """Updates the provider tree for MEM_ENCRYPTION_CONTEXT inventory. Before 2025.2, MEM_ENCRYPTION_CONTEXT inventory and allocations were on @@ -9626,8 +9627,8 @@ class LibvirtDriver(driver.ComputeDriver): root_node = provider_tree.data(nodename) return orc.MEM_ENCRYPTION_CONTEXT in root_node.inventory - def _ensure_memory_encryption_providers(self, inventories_dict, - provider_tree, nodename): + def _ensure_memory_encryption_providers( + self, inventories_dict, provider_tree, nodename): """Ensures MEM_ENCRYPTION_CONTEXT inventory providers exist in the tree for $nodename. @@ -9660,7 +9661,7 @@ class LibvirtDriver(driver.ComputeDriver): if not inventory['total']: if provider_tree.exists(me_rp_name): provider_tree.remove(me_rp_name) - break + continue if not provider_tree.exists(me_rp_name): provider_tree.new_child(me_rp_name, nodename) me_rp = provider_tree.data(me_rp_name) @@ -9847,8 +9848,8 @@ class LibvirtDriver(driver.ComputeDriver): root_node = provider_tree.data(nodename) return orc.VGPU in root_node.inventory - def _ensure_pgpu_providers(self, inventories_dict, provider_tree, - nodename): + def _ensure_pgpu_providers( + self, inventories_dict, provider_tree, nodename): """Ensures GPU inventory providers exist in the tree for $nodename. GPU providers are named $nodename_$gpu-device-id, e.g. @@ -10116,8 +10117,8 @@ class LibvirtDriver(driver.ComputeDriver): rp_uuid, root_node, consumer_uuid, alloc_data, resources, pgpu_rps) - def _update_provider_tree_for_vgpu(self, provider_tree, nodename, - allocations=None): + def _update_provider_tree_for_vgpu( + self, provider_tree, nodename, allocations=None): """Updates the provider tree for VGPU inventory. Before Stein, VGPU inventory and allocations were on the root compute @@ -10173,8 +10174,8 @@ class LibvirtDriver(driver.ComputeDriver): del root_node.inventory[orc.VGPU] provider_tree.update_inventory(nodename, root_node.inventory) - def _update_provider_tree_for_pcpu(self, provider_tree, nodename, - allocations=None): + def _update_provider_tree_for_pcpu( + self, provider_tree, nodename, allocations=None): """Updates the provider tree for PCPU inventory. Before Train, pinned instances consumed VCPU inventory just like