diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py index 827d1e62d0..297aaae8d2 100644 --- a/nova/conf/libvirt.py +++ b/nova/conf/libvirt.py @@ -840,6 +840,13 @@ Related options: cfg.IntOpt('num_memory_encrypted_guests', default=None, min=0, + deprecated_for_removal=True, + deprecated_since='32.0.0', + deprecated_reason=""" +This option is effective for only SEV and has no effect for SEV-ES. Libvirt +is capable to present maximum number of SEV guests and one of SEV-ES guests +since 8.0.0 and this option is no longer necessary. +""", help=""" Maximum number of guests with encrypted memory which can run concurrently on this compute host. diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index f2492c367e..230d99ffdf 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -331,6 +331,7 @@ class ResourceRequest(object): return self._add_resource(orc.MEM_ENCRYPTION_CONTEXT, 1) + self._add_trait(os_traits.HW_CPU_X86_AMD_SEV, 'required') LOG.debug("Added %s=1 to requested resources", orc.MEM_ENCRYPTION_CONTEXT) diff --git a/nova/tests/fixtures/libvirt.py b/nova/tests/fixtures/libvirt.py index 22e3f55d85..638553c1ed 100644 --- a/nova/tests/fixtures/libvirt.py +++ b/nova/tests/fixtures/libvirt.py @@ -2584,7 +2584,9 @@ class LibvirtFixture(fixtures.Fixture): real_exists = os.path.exists def fake_exists(path): - if path == host.SEV_KERNEL_PARAM_FILE: + if path == (host.SEV_KERNEL_PARAM_FILE % 'sev'): + return False + if path == (host.SEV_KERNEL_PARAM_FILE % 'sev-es'): return False return real_exists(path) diff --git a/nova/tests/functional/libvirt/test_report_cpu_traits.py b/nova/tests/functional/libvirt/test_report_cpu_traits.py index 0f8761c0ab..bc1c67dbce 100644 --- a/nova/tests/functional/libvirt/test_report_cpu_traits.py +++ b/nova/tests/functional/libvirt/test_report_cpu_traits.py @@ -13,17 +13,21 @@ # License for the specific language governing permissions and limitations # under the License. +import builtins +import contextlib +import os.path from unittest import mock import os_resource_classes as orc import os_traits as ost +from oslo_utils import versionutils from nova import conf from nova.db import constants as db_const from nova import test from nova.tests.fixtures import libvirt as fakelibvirt from nova.tests.functional.libvirt import integrated_helpers -from nova.virt.libvirt.host import SEV_KERNEL_PARAM_FILE +from nova.virt.libvirt import host as libvirt_host CONF = conf.CONF @@ -51,8 +55,44 @@ class LibvirtReportTraitsTestBase( def _get_amd_sev_rps(self): root_rp = self._get_resource_provider_by_uuid(self.host_uuid) rps = self._get_all_rps_in_a_tree(self.host_uuid) - return [rp for rp in rps - if rp['name'] == '%s_amd_sev' % root_rp['name']] + return { + 'sev': [rp for rp in rps + if rp['name'] == '%s_amd_sev' % root_rp['name']], + 'sev-es': [rp for rp in rps + if rp['name'] == '%s_amd_sev_es' % root_rp['name']] + } + + @contextlib.contextmanager + def _patch_sev_exists(self, sev, sev_es): + real_exists = os.path.exists + + def fake_exists(path): + if path == libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev': + return sev + elif path == libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev_es': + return sev_es + return real_exists(path) + + with mock.patch('os.path.exists') as mock_exists: + mock_exists.side_effect = fake_exists + yield mock_exists + + @contextlib.contextmanager + def _patch_sev_open(self): + real_open = builtins.open + sev_open = mock.mock_open(read_data='1\n') + sev_es_open = mock.mock_open(read_data='1\n') + + def fake_open(path, *args, **kwargs): + if path == libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev': + return sev_open(path) + elif path == libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev_es': + return sev_es_open(path) + return real_open(path, *args, **kwargs) + + with mock.patch('builtins.open') as mock_open: + mock_open.side_effect = fake_open + yield mock_open class LibvirtReportTraitsTests(LibvirtReportTraitsTestBase): @@ -119,50 +159,52 @@ class LibvirtReportTraitsTests(LibvirtReportTraitsTestBase): class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase): STUB_INIT_HOST = False - @test.patch_exists(SEV_KERNEL_PARAM_FILE, False) def setUp(self): - super(LibvirtReportNoSevTraitsTests, self).setUp() - self.start_compute() + with self._patch_sev_exists(False, False): + super(LibvirtReportNoSevTraitsTests, self).setUp() + self.start_compute() def test_sev_trait_off_on(self): - """Test that the compute service reports the SEV trait in the list of - global traits, but doesn't immediately register it on the + """Test that the compute service reports the SEV/SEV-ES trait in + the list of global traits, but doesn't immediately register it on the compute host resource provider in the placement API, due to - the kvm-amd kernel module's sev parameter file being (mocked + the kvm-amd kernel module's sev/sev-es parameter file being (mocked as) absent. - Then test that if the SEV capability appears (again via + Then test that if the SEV/SEV-ES capability appears (again via mocking), after a restart of the compute service, the trait gets registered on 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. + corresponds to the absence or presence of the SEV/SEV-ES capability. """ self.assertFalse(self.compute.driver._host.supports_amd_sev) - - sev_trait = ost.HW_CPU_X86_AMD_SEV + self.assertFalse(self.compute.driver._host.supports_amd_sev_es) global_traits = self._get_all_traits() - self.assertIn(sev_trait, global_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(sev_trait, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV_ES, traits) self.assertMemEncryptionSlotsEqual(self.host_uuid, 0) sev_rps = self._get_amd_sev_rps() - self.assertEqual(0, len(sev_rps)) + self.assertEqual(0, len(sev_rps['sev'])) + self.assertEqual(0, len(sev_rps['sev-es'])) # Now simulate the host gaining SEV functionality. Here we # simulate a kernel update or reconfiguration which causes the # kvm-amd kernel module's "sev" parameter to become available # and set to 1, however it could also happen via a libvirt # upgrade, for instance. - sev_features = \ - fakelibvirt.virConnect._domain_capability_features_with_SEV + sev_features = (fakelibvirt.virConnect. + _domain_capability_features_with_SEV) with test.nested( - self.patch_exists(SEV_KERNEL_PARAM_FILE, True), - self.patch_open(SEV_KERNEL_PARAM_FILE, "1\n"), + self._patch_sev_exists(True, False), + self._patch_sev_open(), mock.patch.object(fakelibvirt.virConnect, '_domain_capability_features', new=sev_features) @@ -173,10 +215,17 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase): # cache in the host object. 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.assertTrue(self.compute.driver._host.supports_amd_sev) + self.assertFalse(self.compute.driver._host.supports_amd_sev_es) - mock_exists.assert_has_calls([mock.call(SEV_KERNEL_PARAM_FILE)]) - mock_open.assert_has_calls([mock.call(SEV_KERNEL_PARAM_FILE)]) + mock_exists.assert_has_calls([ + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev'), + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev_es') + ]) + mock_open.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: @@ -186,81 +235,187 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase): self._run_periodics() # Sanity check that we've still got the trait globally. - self.assertIn(sev_trait, self._get_all_traits()) + 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(sev_trait, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV_ES, traits) self.assertMemEncryptionSlotsEqual(self.host_uuid, 0) sev_rps = self._get_amd_sev_rps() - self.assertEqual(1, len(sev_rps)) - sev_rp_uuid = sev_rps[0]['uuid'] + 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(sev_trait, sev_rp_traits) + self.assertIn(ost.HW_CPU_X86_AMD_SEV, sev_rp_traits) self.assertMemEncryptionSlotsEqual(sev_rp_uuid, db_const.MAX_INT) + self.assertEqual(0, len(sev_rps['sev-es'])) + + # Now simulate the host gaining SEV-ES functionality. Here we + # simulate a kernel update or reconfiguration which causes the + # kvm-amd kernel module's "sev-es" parameter to become available + # and set to 1 + sev_features = (fakelibvirt.virConnect. + _domain_capability_features_with_SEV_max_guests) + with test.nested( + self._patch_sev_exists(True, True), + self._patch_sev_open(), + mock.patch.object(fakelibvirt.virConnect, + '_domain_capability_features', + new=sev_features), + mock.patch.object( + fakelibvirt.Connection, 'getVersion', + return_value=versionutils.convert_version_to_int( + libvirt_host.MIN_QEMU_SEV_ES_VERSION)) + ) as (mock_exists, mock_open, mock_features, mock_get_version): + # Retrigger the detection code. In the real world this + # would be a restart of the compute service. + # As we are changing the domain caps we need to clear the + # cache in the host object. + 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.assertTrue(self.compute.driver._host.supports_amd_sev) + self.assertTrue(self.compute.driver._host.supports_amd_sev_es) + + mock_exists.assert_has_calls([ + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev'), + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev_es') + ]) + mock_open.assert_has_calls([ + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev'), + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev_es') + ]) + + # 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) + + # 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.assertNotIn(ost.HW_CPU_X86_AMD_SEV_ES, 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) + class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase): STUB_INIT_HOST = False - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") - @mock.patch.object( - fakelibvirt.virConnect, '_domain_capability_features', - new=fakelibvirt.virConnect._domain_capability_features_with_SEV) def setUp(self): super(LibvirtReportSevTraitsTests, self).setUp() - self.flags(num_memory_encrypted_guests=16, group='libvirt') - with test.patch_exists(SEV_KERNEL_PARAM_FILE, True): + sev_features = (fakelibvirt.virConnect. + _domain_capability_features_with_SEV_max_guests) + with test.nested( + self._patch_sev_exists(True, True), + self._patch_sev_open(), + mock.patch.object(fakelibvirt.virConnect, + '_domain_capability_features', + new=sev_features), + mock.patch.object( + fakelibvirt.Connection, 'getVersion', + return_value=versionutils.convert_version_to_int( + libvirt_host.MIN_QEMU_SEV_ES_VERSION)) + ) as (mock_exists, mock_open, mock_features, mock_get_version): self.start_compute() def test_sev_trait_on_off(self): - """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 + """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 capability disappears (again via + 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 capability. + corresponds to the absence or presence of the SEV/SEV-ES capability. """ - self.assertTrue(self.compute.driver._host.supports_amd_sev) - sev_trait = ost.HW_CPU_X86_AMD_SEV + 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(sev_trait, global_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(sev_trait, traits) + 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_rp_uuid = sev_rps[0]['uuid'] - sev_rp_traits = self._get_provider_traits(sev_rp_uuid) - self.assertIn(sev_trait, sev_rp_traits) - self.assertMemEncryptionSlotsEqual(sev_rp_uuid, 16) - # Now simulate the host losing SEV functionality. Here we + 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-ES functionality. Here we # simulate a kernel downgrade or reconfiguration which causes - # the kvm-amd kernel module's "sev" parameter to become + # the kvm-amd kernel module's "sev-es" parameter to become # unavailable, however it could also happen via a libvirt # downgrade, for instance. - with self.patch_exists(SEV_KERNEL_PARAM_FILE, False) as mock_exists: + sev_features = (fakelibvirt.virConnect. + _domain_capability_features_with_SEV) + with test.nested( + self._patch_sev_exists(True, 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.assertFalse(self.compute.driver._host.supports_amd_sev) + self.compute.driver._host._supports_amd_sev_es = None + self.assertTrue(self.compute.driver._host.supports_amd_sev) + self.assertFalse(self.compute.driver._host.supports_amd_sev_es) - mock_exists.assert_has_calls([mock.call(SEV_KERNEL_PARAM_FILE)]) + mock_exists.assert_has_calls([ + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev'), + mock.call(libvirt_host.SEV_KERNEL_PARAM_FILE % 'sev_es') + ]) + mock_open.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: @@ -270,13 +425,63 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase): self._run_periodics() # Sanity check that we've still got the trait globally. - self.assertIn(sev_trait, self._get_all_traits()) + 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(sev_trait, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV, traits) + self.assertNotIn(ost.HW_CPU_X86_AMD_SEV_ES, traits) # NOTE(tkajinam): Currently the sev rp is not deleted after sev # support is turned off. This follows the existing behavior for # other resources such as vGPU. # sev_rps = self._get_amd_sev_rps() - # self.assertEqual(0, len(sev_rps)) + # 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) + + # NOTE(tkajinam): Currently the sev rp is not deleted after sev + # support is turned off. This follows the existing behavior for + # other resources such as vGPU. + # sev_rps = self._get_amd_sev_rps() + # self.assertEqual(0, len(sev_rps['sev'])) diff --git a/nova/tests/functional/libvirt/test_reshape.py b/nova/tests/functional/libvirt/test_reshape.py index f978f6a2c3..89f5f0bceb 100644 --- a/nova/tests/functional/libvirt/test_reshape.py +++ b/nova/tests/functional/libvirt/test_reshape.py @@ -15,6 +15,7 @@ import copy import io from unittest import mock +import os_traits from oslo_config import cfg from oslo_log import log as logging from oslo_utils.fixture import uuidsentinel @@ -262,9 +263,7 @@ class SevResphapeTests(base.ServersTestBase): # create the MEM_ENCRYPTION_CONTEXT resource in placement manually, # to simulate the old layout. - compute_rp_uuid = self.placement.get( - '/resource_providers?name=compute1').body[ - 'resource_providers'][0]['uuid'] + compute_rp_uuid = self._get_provider_uuid_by_name('compute1') inventories = self.placement.get( '/resource_providers/%s/inventories' % compute_rp_uuid).body inventories['inventories']['MEM_ENCRYPTION_CONTEXT'] = { @@ -277,6 +276,9 @@ class SevResphapeTests(base.ServersTestBase): self.placement.put( '/resource_providers/%s/inventories' % compute_rp_uuid, inventories) + 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) # create a server before reshape with mock.patch('nova.virt.libvirt.driver.LibvirtDriver.' @@ -287,51 +289,39 @@ class SevResphapeTests(base.ServersTestBase): # verify that the inventory, usages and allocation are correct before # the reshape - compute_inventory = self.placement.get( - '/resource_providers/%s/inventories' % compute_rp_uuid).body[ - 'inventories'] + compute_inventories = self._get_provider_inventory(compute_rp_uuid) self.assertEqual( - 16, compute_inventory['MEM_ENCRYPTION_CONTEXT']['total']) - compute_usages = self.placement.get( - '/resource_providers/%s/usages' % compute_rp_uuid).body[ - 'usages'] + 16, compute_inventories['MEM_ENCRYPTION_CONTEXT']['total']) + compute_usages = self._get_provider_usages(compute_rp_uuid) self.assertEqual(1, compute_usages['MEM_ENCRYPTION_CONTEXT']) # restart the compute service to trigger reshape with mock.patch('nova.virt.libvirt.host.Host.supports_amd_sev', - return_value=True): + return_value=True), \ + mock.patch('nova.virt.libvirt.host.Host.supports_amd_sev_es', + return_value=False): self.compute = self.restart_compute_service(self.hostname) # verify that the inventory, usages and allocation are correct after # the reshape - compute_inventory = self.placement.get( - '/resource_providers/%s/inventories' % compute_rp_uuid).body[ - 'inventories'] - self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_inventory) - compute_usages = self.placement.get( - '/resource_providers/%s/usages' % compute_rp_uuid).body[ - 'usages'] + compute_inventories = self._get_provider_inventory(compute_rp_uuid) + self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_inventories) + compute_usages = self._get_provider_usages(compute_rp_uuid) self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_usages) - sev_rp_uuid = self.placement.get( - '/resource_providers?name=compute1_amd_sev').body[ - 'resource_providers'][0]['uuid'] - sev_inventory = self.placement.get( - '/resource_providers/%s/inventories' % sev_rp_uuid).body[ - 'inventories'] + 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_inventory['MEM_ENCRYPTION_CONTEXT']['total']) - sev_usages = self.placement.get( - '/resource_providers/%s/usages' % sev_rp_uuid).body[ - 'usages'] + 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_traits = self._get_provider_traits(sev_rp_uuid) + self.assertIn(os_traits.HW_CPU_X86_AMD_SEV, sev_traits) # create a new server after reshape post_server = self._create_server( image_uuid=uuidsentinel.mem_enc_image_id) self.addCleanup(self._delete_server, post_server) - compute_usages = self.placement.get( - '/resource_providers/%s/usages' % sev_rp_uuid).body[ - 'usages'] - self.assertEqual(2, compute_usages['MEM_ENCRYPTION_CONTEXT']) + sev_usages = self._get_provider_usages(sev_rp_uuid) + self.assertEqual(2, sev_usages['MEM_ENCRYPTION_CONTEXT']) diff --git a/nova/tests/functional/regressions/test_bug_1928063.py b/nova/tests/functional/regressions/test_bug_1928063.py index 94d7b8122c..3e9daec337 100644 --- a/nova/tests/functional/regressions/test_bug_1928063.py +++ b/nova/tests/functional/regressions/test_bug_1928063.py @@ -15,10 +15,8 @@ from unittest import mock from oslo_utils.fixture import uuidsentinel as uuids -from nova import test from nova.tests.fixtures import libvirt as fakelibvirt from nova.tests.functional.libvirt import base -from nova.virt.libvirt.host import SEV_KERNEL_PARAM_FILE class TestSEVInstanceReboot(base.ServersTestBase): @@ -30,16 +28,16 @@ class TestSEVInstanceReboot(base.ServersTestBase): """ microversion = 'latest' - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") @mock.patch.object( fakelibvirt.virConnect, '_domain_capability_features', new=fakelibvirt.virConnect._domain_capability_features_with_SEV) def setUp(self): super().setUp() - # Configure the compute to allow SEV based instances and then start - self.flags(num_memory_encrypted_guests=16, group='libvirt') - with test.patch_exists(SEV_KERNEL_PARAM_FILE, True): + with mock.patch('nova.virt.libvirt.host.Host.supports_amd_sev', + return_value=True), \ + mock.patch('nova.virt.libvirt.host.Host.supports_amd_sev_es', + return_value=False): self.start_compute() # Create a SEV enabled image for the test diff --git a/nova/tests/functional/test_nova_manage.py b/nova/tests/functional/test_nova_manage.py index df4c58ce25..a2221563c3 100644 --- a/nova/tests/functional/test_nova_manage.py +++ b/nova/tests/functional/test_nova_manage.py @@ -2705,7 +2705,7 @@ class TestNovaManageLimits(integrated_helpers.ProviderUsageBaseTestCase): self.assertIn('SUCCESS', self.output.getvalue()) self.assertEqual(0, result) - def _add_to_inventory(self, resource): + def _add_to_inventory(self, resource, trait=None): # Add resource to inventory for both computes. for rp in self._get_all_providers(): inv = self._get_provider_inventory(rp['uuid']) @@ -2713,6 +2713,10 @@ class TestNovaManageLimits(integrated_helpers.ProviderUsageBaseTestCase): self._update_inventory( rp['uuid'], {'inventories': inv, 'resource_provider_generation': rp['generation']}) + if trait is not None: + traits = self._get_provider_traits(rp['uuid']) + traits.append(trait) + self._set_provider_traits(rp['uuid'], traits) def _create_flavor_and_add_to_inventory(self, resource): # Create a flavor for the resource. @@ -2796,7 +2800,8 @@ class TestNovaManageLimits(integrated_helpers.ProviderUsageBaseTestCase): flavor_id = self._create_flavor( name='fakeflavor', vcpu=1, memory_mb=512, disk=1, ephemeral=0, extra_spec=extra_spec) - self._add_to_inventory('MEM_ENCRYPTION_CONTEXT') + self._add_to_inventory( + 'MEM_ENCRYPTION_CONTEXT', 'HW_CPU_X86_AMD_SEV') image_id = self._create_image( metadata={'hw_firmware_type': 'uefi'})['id'] self._create_server( diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py index e0ca310e1f..a17f89d385 100644 --- a/nova/tests/unit/scheduler/test_utils.py +++ b/nova/tests/unit/scheduler/test_utils.py @@ -2333,13 +2333,17 @@ class TestEncryptedMemoryTranslation(TestUtilsBase): 'MEMORY_MB': 1024, 'DISK_GB': 15, } + required_traits = [] if mem_encryption_context: expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1 + required_traits = ['HW_CPU_X86_AMD_SEV'] expected = FakeResourceRequest() expected._rg_by_id[None] = objects.RequestGroup( use_same_provider=False, - resources=expected_resources) + resources=expected_resources, + required_traits=set(required_traits) + ) return expected def _test_encrypted_memory_support_not_required(self, extra_specs, diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 331037d08a..efb2825cc3 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -7693,7 +7693,7 @@ class LibvirtConnTestCase(test.NoDBTestCase, self.assertEqual(cfg.devices[5].rate_bytes, 1024) self.assertEqual(cfg.devices[5].rate_period, 2) - @test.patch_exists(SEV_KERNEL_PARAM_FILE, result=False, other=True) + @test.patch_exists(SEV_KERNEL_PARAM_FILE % 'sev', result=False, other=True) def test_get_guest_config_with_rng_backend(self): self.flags(virt_type='kvm', rng_dev_path='/dev/hw_rng', @@ -8341,7 +8341,7 @@ class LibvirtConnTestCase(test.NoDBTestCase, @mock.patch.object(libvirt_driver.LibvirtDriver, "_get_guest_storage_config") @mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support") - @test.patch_exists(SEV_KERNEL_PARAM_FILE, result=False, other=True) + @test.patch_exists(SEV_KERNEL_PARAM_FILE % 'sev', result=False, other=True) def test_get_guest_config_aarch64(self, mock_numa, mock_storage): TEST_AMOUNT_OF_PCIE_SLOTS = 8 CONF.set_override("num_pcie_ports", TEST_AMOUNT_OF_PCIE_SLOTS, @@ -8378,7 +8378,7 @@ class LibvirtConnTestCase(test.NoDBTestCase, @mock.patch.object(libvirt_driver.LibvirtDriver, "_get_guest_storage_config") @mock.patch.object(libvirt_driver.LibvirtDriver, "_has_numa_support") - @test.patch_exists(SEV_KERNEL_PARAM_FILE, result=False, other=True) + @test.patch_exists(SEV_KERNEL_PARAM_FILE % 'sev', result=False, other=True) def test_get_guest_config_aarch64_with_graphics( self, mock_numa, mock_storage, ): @@ -23066,6 +23066,7 @@ class TestUpdateProviderTree(test.NoDBTestCase): # Use total=0 for MEM_ENCRYPTION_CONTEXT self.driver._host._supports_amd_sev = True self.driver._host._max_sev_guests = 0 + self.driver._host._supports_amd_sev_es = False # Before we update_provider_tree, we have 2 providers from setUp(): # self.cn_rp and self.shared_rp and they are both empty {}. self.assertEqual(2, len(self.pt.get_provider_uuids())) @@ -23190,6 +23191,7 @@ class TestUpdateProviderTree(test.NoDBTestCase): def test_update_provider_tree_with_memory_encryption(self): self.driver._host._supports_amd_sev = True self.driver._host._max_sev_guests = 16 + self.driver._host._supports_amd_sev_es = False self._test_update_provider_tree() inventory = self._get_inventory() # root compute node provider inventory is unchanged @@ -23530,6 +23532,7 @@ class TestUpdateProviderTree(test.NoDBTestCase): def test_update_provider_tree_for_memory_encryption_reshape(self): self.driver._host._supports_amd_sev = True self.driver._host._max_sev_guests = 16 + self.driver._host._supports_amd_sev_es = False # First create a provider tree with MEM_ENCRYPTION_CONTEXT inventory on # the root node provider. inventory = self._get_inventory() @@ -23641,6 +23644,7 @@ class TestUpdateProviderTree(test.NoDBTestCase): def test_update_provider_tree_for_memory_encryption_reshape_fails(self): self.driver._host._supports_amd_sev = True self.driver._host._max_sev_guests = 16 + self.driver._host._supports_amd_sev_es = False # First create a provider tree with MEM_ENCRYPTION_CONTEXT inventory on # the root node provider. inventory = self._get_inventory() @@ -31100,6 +31104,13 @@ class TestLibvirtSEV(test.NoDBTestCase): self.useFixture(nova_fixtures.LibvirtFixture()) self.driver = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + def fake_exists(path): + if path == '/sys/module/kvm_amd/parameters/sev': + return True + return False + + self.stub_out('os.path.exists', fake_exists) + @mock.patch.object(os.path, 'exists', new=mock.Mock(return_value=False)) class TestLibvirtSEVUnsupported(TestLibvirtSEV): @@ -31127,9 +31138,18 @@ class TestLibvirtSEVUnsupported(TestLibvirtSEV): @mock.patch.object(vc, '_domain_capability_features', new=vc._domain_capability_features_with_SEV) class TestLibvirtSEVSupportedNoMaxGuests(TestLibvirtSEV): + def setUp(self): + super(TestLibvirtSEVSupportedNoMaxGuests, self).setUp() + + def fake_exists(path): + if path == '/sys/module/kvm_amd/parameters/sev': + return True + return False + + self.stub_out('os.path.exists', fake_exists) + """Libvirt driver tests for when AMD SEV support is present.""" - @test.patch_exists(SEV_KERNEL_PARAM_FILE, True) - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") + @test.patch_open(SEV_KERNEL_PARAM_FILE % 'sev', "1\n") def test_get_memory_encryption_inventories_unlimited(self): self.assertEqual({ 'amd_sev': { @@ -31143,8 +31163,7 @@ class TestLibvirtSEVSupportedNoMaxGuests(TestLibvirtSEV): } }, self.driver._get_memory_encryption_inventories()) - @test.patch_exists(SEV_KERNEL_PARAM_FILE, True) - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") + @test.patch_open(SEV_KERNEL_PARAM_FILE % 'sev', "1\n") def test_get_memory_encryption_inventories_config_non_zero_supported(self): self.flags(num_memory_encrypted_guests=16, group='libvirt') self.assertEqual({ @@ -31159,8 +31178,7 @@ class TestLibvirtSEVSupportedNoMaxGuests(TestLibvirtSEV): } }, self.driver._get_memory_encryption_inventories()) - @test.patch_exists(SEV_KERNEL_PARAM_FILE, True) - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") + @test.patch_open(SEV_KERNEL_PARAM_FILE % 'sev', "1\n") def test_get_memory_encryption_inventories_config_zero_supported(self): self.flags(num_memory_encrypted_guests=0, group='libvirt') self.assertEqual({ @@ -31179,9 +31197,18 @@ class TestLibvirtSEVSupportedNoMaxGuests(TestLibvirtSEV): @mock.patch.object(vc, '_domain_capability_features', new=vc._domain_capability_features_with_SEV_max_guests) class TestLibvirtSEVSupportedMaxGuests(TestLibvirtSEV): + def setUp(self): + super(TestLibvirtSEVSupportedMaxGuests, self).setUp() + + def fake_exists(path): + if path == '/sys/module/kvm_amd/parameters/sev': + return True + return False + + self.stub_out('os.path.exists', fake_exists) + """Libvirt driver tests for when AMD SEV support is present.""" - @test.patch_exists(SEV_KERNEL_PARAM_FILE, True) - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") + @test.patch_open(SEV_KERNEL_PARAM_FILE % 'sev', "1\n") @mock.patch.object(libvirt_driver.LOG, 'warning') def test_get_memory_encryption_inventories_no_override(self, mock_log): self.assertEqual({ @@ -31197,8 +31224,7 @@ class TestLibvirtSEVSupportedMaxGuests(TestLibvirtSEV): }, self.driver._get_memory_encryption_inventories()) mock_log.assert_not_called() - @test.patch_exists(SEV_KERNEL_PARAM_FILE, True) - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") + @test.patch_open(SEV_KERNEL_PARAM_FILE % 'sev', "1\n") @mock.patch.object(libvirt_driver.LOG, 'warning') def test_get_memory_encryption_inventories_override_more(self, mock_log): self.flags(num_memory_encrypted_guests=120, group='libvirt') @@ -31217,8 +31243,7 @@ class TestLibvirtSEVSupportedMaxGuests(TestLibvirtSEV): 'Host is configured with libvirt.num_memory_encrypted_guests ' 'set to %d, but supports only %d.', 120, 100) - @test.patch_exists(SEV_KERNEL_PARAM_FILE, True) - @test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n") + @test.patch_open(SEV_KERNEL_PARAM_FILE % 'sev', "1\n") @mock.patch.object(libvirt_driver.LOG, 'warning') def test_get_memory_encryption_inventories_override_less(self, mock_log): self.flags(num_memory_encrypted_guests=80, group='libvirt') diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py index 8cb9bb6499..cb86c9a851 100644 --- a/nova/tests/unit/virt/libvirt/test_host.py +++ b/nova/tests/unit/virt/libvirt/test_host.py @@ -24,6 +24,7 @@ from eventlet import tpool from oslo_serialization import jsonutils from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import uuidutils +from oslo_utils import versionutils import testtools from nova.compute import vm_states @@ -2194,6 +2195,86 @@ class TestLibvirtSEVSupported(TestLibvirtSEV): self.assertTrue(self.host.supports_amd_sev) +@ddt.ddt +class TestLibvirtSEVESUnsupported(TestLibvirtSEV): + @mock.patch.object(os.path, 'exists', return_value=False) + def test_kernel_parameter_missing(self, fake_exists): + self.assertFalse(self.host._kernel_supports_amd_sev(model='sev-es')) + fake_exists.assert_called_once_with( + '/sys/module/kvm_amd/parameters/sev_es') + + @ddt.data( + ('0\n', False), + ('N\n', False), + ('1\n', True), + ('Y\n', True), + ) + @ddt.unpack + @mock.patch.object(os.path, 'exists', return_value=True) + def test_kernel_parameter( + self, sev_param_value, expected_support, mock_exists + ): + with mock.patch( + 'builtins.open', mock.mock_open(read_data=sev_param_value) + ): + self.assertIs( + expected_support, + self.host._kernel_supports_amd_sev(model='sev-es') + ) + mock_exists.assert_called_once_with( + '/sys/module/kvm_amd/parameters/sev_es') + + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch('builtins.open', mock.mock_open(read_data="1\n")) + def test_unsupported_without_feature(self, fake_exists): + self.assertFalse(self.host.supports_amd_sev_es) + + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch('builtins.open', mock.mock_open(read_data="1\n")) + @mock.patch.object(vc, '_domain_capability_features', + new=vc._domain_capability_features_with_SEV_unsupported) + def test_unsupported_with_feature(self, fake_exists): + self.assertFalse(self.host.supports_amd_sev_es) + + def test_non_x86_architecture(self): + fake_caps_xml = ''' + + + cef19ce0-0ca2-11df-855d-b19fbce37686 + + aarch64 + + +''' + with mock.patch.object(fakelibvirt.virConnect, 'getCapabilities', + return_value=fake_caps_xml): + self.assertFalse(self.host.supports_amd_sev_es) + + @mock.patch.object(fakelibvirt.Connection, 'getVersion', + return_value=versionutils.convert_version_to_int( + host.MIN_QEMU_SEV_ES_VERSION) - 1) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch('builtins.open', mock.mock_open(read_data="1\n")) + @mock.patch.object(vc, '_domain_capability_features', + new=vc._domain_capability_features_with_SEV) + def test_unsupported_with_qemu_too_old(self, fake_exists, get_version): + self.assertFalse(self.host.supports_amd_sev_es) + + +class TestLibvirtSEVESSupported(TestLibvirtSEV): + """Libvirt driver tests for when AMD SEV support is present.""" + + @mock.patch.object(fakelibvirt.Connection, 'getVersion', + return_value=versionutils.convert_version_to_int( + host.MIN_QEMU_SEV_ES_VERSION)) + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch('builtins.open', mock.mock_open(read_data="1\n")) + @mock.patch.object(vc, '_domain_capability_features', + new=vc._domain_capability_features_with_SEV) + def test_supported_with_feature(self, fake_exists, get_version): + self.assertTrue(self.host.supports_amd_sev_es) + + class LibvirtTpoolProxyTestCase(test.NoDBTestCase): def setUp(self): super(LibvirtTpoolProxyTestCase, self).setUp() diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index f71458fc30..9f8f76e346 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -9809,23 +9809,21 @@ class LibvirtDriver(driver.ComputeDriver): "%d, but is not SEV-capable.", conf_slots) return {} - slots = db_const.MAX_INT + sev_slots = db_const.MAX_INT - # NOTE(tkajinam): Current nova supports SEV only so we ignore SEV-ES if self._host.max_sev_guests is not None: - slots = self._host.max_sev_guests + sev_slots = self._host.max_sev_guests if conf_slots is not None: - if conf_slots > slots: + if conf_slots > sev_slots: LOG.warning("Host is configured with " "libvirt.num_memory_encrypted_guests set to %d, " - "but supports only %d.", conf_slots, slots) - slots = min(slots, conf_slots) + "but supports only %d.", conf_slots, sev_slots) + sev_slots = min(sev_slots, conf_slots) - LOG.debug("Available memory encrypted slots: AMD SEV=%d", slots) - return { + inventories = { 'amd_sev': { - 'total': slots, + 'total': sev_slots, 'step_size': 1, 'max_unit': 1, 'min_unit': 1, @@ -9835,6 +9833,24 @@ class LibvirtDriver(driver.ComputeDriver): } } + sev_es_slots = 0 + if self._host.supports_amd_sev_es: + if self._host.max_sev_es_guests is not None: + sev_es_slots = self._host.max_sev_es_guests + inventories['amd_sev_es'] = { + 'total': sev_es_slots, + 'step_size': 1, + 'max_unit': 1, + 'min_unit': 1, + 'allocation_ratio': 1.0, + 'reserved': 0, + 'traits': [ot.HW_CPU_X86_AMD_SEV_ES] + } + + LOG.debug("Available memory encrypted slots: " + "AMD SEV=%d SEV-ES=%d", sev_slots, sev_es_slots) + return inventories + @property def static_traits(self) -> ty.Dict[str, bool]: if self._static_traits is not None: diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py index 7b6e7ecc5c..63ba17d81b 100644 --- a/nova/virt/libvirt/host.py +++ b/nova/virt/libvirt/host.py @@ -86,7 +86,7 @@ CONF = nova.conf.CONF # This is *not* the complete list of supported hypervisor drivers. HV_DRIVER_QEMU = "QEMU" -SEV_KERNEL_PARAM_FILE = '/sys/module/kvm_amd/parameters/sev' +SEV_KERNEL_PARAM_FILE = '/sys/module/kvm_amd/parameters/%s' # These are taken from the spec # https://github.com/qemu/qemu/blob/v5.2.0/docs/interop/firmware.json @@ -96,6 +96,8 @@ QEMU_FIRMWARE_DESCRIPTOR_PATHS = [ # we intentionally ignore '$XDG_CONFIG_HOME/qemu/firmware' ] +MIN_QEMU_SEV_ES_VERSION = (8, 0, 0) + def _get_loaders(): if not any( @@ -162,6 +164,7 @@ class Host(object): # kernel, QEMU, and/or libvirt. These are determined on demand and # memoized by various properties below self._supports_amd_sev: ty.Optional[bool] = None + self._supports_amd_sev_es: ty.Optional[bool] = None self._max_sev_guests: ty.Optional[int] = None self._max_sev_es_guests: ty.Optional[int] = None self._supports_uefi: ty.Optional[bool] = None @@ -1936,14 +1939,18 @@ class Host(object): # 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) + def _kernel_supports_amd_sev(self, model='sev') -> bool: + """Determine if the kernel supports AMD SEV for guests. + """ + kernel_param_file = SEV_KERNEL_PARAM_FILE % model.replace('-', '_') + + if not os.path.exists(kernel_param_file): + LOG.debug("%s does not exist", kernel_param_file) return False - with open(SEV_KERNEL_PARAM_FILE) as f: + with open(kernel_param_file) as f: content = f.read() - LOG.debug("%s contains [%s]", SEV_KERNEL_PARAM_FILE, content) + LOG.debug("%s contains [%s]", kernel_param_file, content) return strutils.bool_from_string(content) @property @@ -1989,6 +1996,34 @@ class Host(object): LOG.debug("No AMD SEV support detected for any (arch, machine_type)") return self._supports_amd_sev + @property + def supports_amd_sev_es(self) -> bool: + """Determine if the host supports AMD SEV-ES for guests. + + Returns a boolean indicating whether AMD SEV (Secure Encrypted + Virtualization-Encrypted State) is supported. This is conditional on + support in the hardware, kernel, qemu, and libvirt. SEV-ES is enabled + in kernel only when SEV is enabled, so this check depends on + the supports_amd_sev check. + """ + if self._supports_amd_sev_es is not None: + return self._supports_amd_sev_es + + self._supports_amd_sev_es = False + if not self.supports_amd_sev: + return self._supports_amd_sev_es + + if not self._kernel_supports_amd_sev(model='sev-es'): + LOG.info("kernel doesn't support AMD SEV-ES") + return self._supports_amd_sev_es + + if not self.has_min_version(hv_ver=MIN_QEMU_SEV_ES_VERSION): + LOG.info("QEMU doesn't support AMD SEV-ES") + return self._supports_amd_sev_es + + self._supports_amd_sev_es = True + return self._supports_amd_sev_es + @property def max_sev_guests(self) -> ty.Optional[int]: """Determine maximum number of guests with AMD SEV. diff --git a/releasenotes/notes/libvirt-deprecate-num_memory_encrypted_guests-61dc3dcd2ce9d8ea.yaml b/releasenotes/notes/libvirt-deprecate-num_memory_encrypted_guests-61dc3dcd2ce9d8ea.yaml new file mode 100644 index 0000000000..2020ce795a --- /dev/null +++ b/releasenotes/notes/libvirt-deprecate-num_memory_encrypted_guests-61dc3dcd2ce9d8ea.yaml @@ -0,0 +1,9 @@ +--- +deprecations: + - | + The ``[libvirt] num_memory_encrypted_guests`` option has been deprecated + and will be removed in a future release. The option will be completely + replaced by the number of SEV-encrypted guests presented by domain + capabilities API in libvirt, which is available since version 8.0.0 . + The libvirt's API is more feature complete and supports detecting the limit + for SEV-ES-encrypted guests.