Merge "Detect AMD SEV-ES support"
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
|
||||
|
@@ -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(
|
||||
|
@@ -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,
|
||||
|
@@ -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')
|
||||
|
@@ -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()
|
||||
|
@@ -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:
|
||||
|
@@ -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