Merge "Detect AMD SEV-ES support"

This commit is contained in:
Zuul
2025-08-28 20:36:36 +00:00
committed by Gerrit Code Review
13 changed files with 505 additions and 127 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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']))

View File

@@ -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'])

View File

@@ -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

View File

@@ -2702,7 +2702,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'])
@@ -2710,6 +2710,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.
@@ -2793,7 +2797,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(

View File

@@ -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,

View File

@@ -7792,7 +7792,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',
@@ -8440,7 +8440,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,
@@ -8477,7 +8477,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,
):
@@ -23133,6 +23133,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()))
@@ -23257,6 +23258,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
@@ -23597,6 +23599,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()
@@ -23708,6 +23711,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()
@@ -31165,6 +31169,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):
@@ -31192,9 +31203,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': {
@@ -31208,8 +31228,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({
@@ -31224,8 +31243,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({
@@ -31244,9 +31262,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({
@@ -31262,8 +31289,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')
@@ -31282,8 +31308,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')

View File

@@ -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 = '''
<capabilities>
<host>
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
<cpu>
<arch>aarch64</arch>
</cpu>
</host>
</capabilities>'''
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()

View File

@@ -9748,23 +9748,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,
@@ -9774,6 +9772,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:

View File

@@ -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.

View File

@@ -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.