Detect AMD SEV-ES support
Detect AMD SEV-ES support by kernel/qemu/libvirt and generate a nested RP for ASID slots for SEV-ES under the compute node RP. Deprecate the [libvirt] num_memory_encryption_guests option because the option is effective only for SEV, and now the maximum numbers for SEV/SEV-ES guests can be detected by domain capabilities presented by libvirt. Note that creating an instance with memory encryption enabled now requires AMD SEV trait, because these instances can't run with SEV-ES slots, which are added by this change. Partially-Implements: blueprint amd-sev-es-libvirt-support Change-Id: I5968e75325b989225ed1fc6921257751ae227a0b Signed-off-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
4
nova/tests/fixtures/libvirt.py
vendored
4
nova/tests/fixtures/libvirt.py
vendored
@@ -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)
|
||||
|
||||
|
||||
@@ -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']))
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user