diff --git a/doc/source/admin/sev.rst b/doc/source/admin/sev.rst
index 2f55bf6880..e2747074b5 100644
--- a/doc/source/admin/sev.rst
+++ b/doc/source/admin/sev.rst
@@ -93,15 +93,14 @@ steps:
Since version 8.0.0, libvirt exposes maximum number of SEV guests
which can run concurrently in its host, so the limit is automatically
- detected using this feature.
+ detected using this feature. So it is not necessary to configure this option.
However in case an older version of libvirt is used, it is not possible for
Nova to programmatically detect the correct value and Nova imposes no limit.
So this configuration option serves as a stop-gap, allowing the cloud
operator the option of providing this value manually.
- This option also allows the cloud operator to set the limit lower than
- the actual hard limit.
+ This option has been deprecated and will be removed in a future release.
.. note::
@@ -175,14 +174,11 @@ enable SEV for a flavor:
$ openstack flavor set FLAVOR-NAME \
--property hw:mem_encryption=true
-These do not inherently cause a preference for SEV-capable hardware,
-but for now SEV is the only way of fulfilling the requirement for
-memory encryption. However in the future, support for other
-hardware-level guest memory encryption technology such as Intel MKTME
-may be added. If a guest specifically needs to be booted using SEV
-rather than any other memory encryption technology, it is possible to
-ensure this by setting the :nova:extra-spec:`trait{group}:HW_CPU_X86_AMD_SEV`
-extra spec or equivalent image metadata property to ``required``.
+It is also possible to use SEV-ES, instead of SEV, by setting
+the :nova:extra-spec:`hw:mem_encryption_model` extra spec to ``amd-sev-es``, or
+by using an image with the ``hw_mem_encryption_model`` property set to
+``amd-sev-es``. In case the extra spec and the property are unset or set to
+``amd-sev`` then SEV is used.
In all cases, SEV instances can only be booted from images which have
the ``hw_firmware_type`` property set to ``uefi``, and only when the
diff --git a/nova/api/validation/extra_specs/hw.py b/nova/api/validation/extra_specs/hw.py
index 9a9e8f9964..9f180de7e4 100644
--- a/nova/api/validation/extra_specs/hw.py
+++ b/nova/api/validation/extra_specs/hw.py
@@ -407,6 +407,22 @@ feature_flag_validators = [
'description': 'Whether to enable memory encryption',
},
),
+ base.ExtraSpecValidator(
+ name='hw:mem_encryption_model',
+ description=(
+ 'CPU feature used for memory encryption of the guest. '
+ 'This has no effect unless hw:mem_encryption (or equivalent '
+ 'image property) is set to True.'
+ ),
+ value={
+ 'type': str,
+ 'description': 'A CPU feature used for memory encryption',
+ 'enum': [
+ 'amd-sev',
+ 'amd-sev-es',
+ ],
+ },
+ ),
base.ExtraSpecValidator(
name='hw:pmem',
description=(
diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py
index 230d99ffdf..58a52ab02d 100644
--- a/nova/scheduler/utils.py
+++ b/nova/scheduler/utils.py
@@ -184,7 +184,7 @@ class ResourceRequest(object):
if disk:
res_req._add_resource(orc.DISK_GB, disk)
- res_req._translate_memory_encryption(request_spec.flavor, image)
+ res_req._translate_mem_encryption_request(request_spec.flavor, image)
res_req._translate_vpmems_request(request_spec.flavor)
@@ -317,24 +317,32 @@ class ResourceRequest(object):
LOG.debug("Requiring emulated TPM support via trait %s and %s.",
version_trait, model_trait)
- def _translate_memory_encryption(self, flavor, image):
+ def _translate_mem_encryption_request(self, flavor, image):
"""When the hw:mem_encryption extra spec or the hw_mem_encryption
image property are requested, translate into a request for
resources:MEM_ENCRYPTION_CONTEXT=1 which requires a slot on a
- host which can support encryption of the guest memory.
+ host which can support encryption of the guest memory. Also require
+ the specific trait for the requested memory encryption feature of CPU.
"""
# NOTE(aspiers): In theory this could raise FlavorImageConflict,
# but we already check it in the API layer, so that should never
- # happen.
- if not hardware.get_mem_encryption_constraint(flavor, image):
+ # happen
+ mem_enc_config = hardware.get_mem_encryption_constraint(flavor, image)
+ if not mem_enc_config:
# No memory encryption required, so no further action required.
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)
+ me_trait = os_traits.HW_CPU_X86_AMD_SEV
+ if mem_enc_config.model == obj_fields.MemEncryptionModel.AMD_SEV_ES:
+ me_trait = os_traits.HW_CPU_X86_AMD_SEV_ES
+ self._add_trait(me_trait, 'required')
+ LOG.debug("Requiring memory encryption model %s via trait %s",
+ mem_enc_config.model, me_trait)
+
def _translate_vpmems_request(self, flavor):
"""When the hw:pmem extra spec is present, require hosts which can
provide enough vpmem resources.
diff --git a/nova/tests/functional/libvirt/test_reshape.py b/nova/tests/functional/libvirt/test_reshape.py
index 89f5f0bceb..ad0b68e362 100644
--- a/nova/tests/functional/libvirt/test_reshape.py
+++ b/nova/tests/functional/libvirt/test_reshape.py
@@ -253,8 +253,9 @@ class SevResphapeTests(base.ServersTestBase):
hw_mem_enc_image['properties']['hw_mem_encryption'] = True
self.glance.create(admin_context, hw_mem_enc_image)
- @mock.patch('nova.virt.libvirt.driver.LibvirtDriver._guest_configure_sev')
- def test_create_servers_with_amd_sev(self, mock_configure_sev):
+ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
+ '_guest_configure_mem_encryption')
+ def test_create_servers_with_amd_sev(self, mock_configure_me):
self.hostname = self.start_compute(
hostname='compute1',
)
diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py
index a17f89d385..fdad6a820c 100644
--- a/nova/tests/unit/scheduler/test_utils.py
+++ b/nova/tests/unit/scheduler/test_utils.py
@@ -2327,16 +2327,22 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
reqspec = self._get_request_spec(extra_specs, image)
return utils.ResourceRequest.from_request_spec(reqspec)
- def _get_expected_resource_request(self, mem_encryption_context):
+ def _get_expected_resource_request(self, mem_encryption_model):
expected_resources = {
'VCPU': 1,
'MEMORY_MB': 1024,
'DISK_GB': 15,
}
required_traits = []
- if mem_encryption_context:
+ if mem_encryption_model == 'amd-sev':
expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1
required_traits = ['HW_CPU_X86_AMD_SEV']
+ elif mem_encryption_model == 'amd-sev-es':
+ expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1
+ required_traits = ['HW_CPU_X86_AMD_SEV_ES']
+ elif mem_encryption_model is not None:
+ self.fail('invalid mem_encryption_model: %s'
+ % mem_encryption_model)
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
@@ -2349,7 +2355,7 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
def _test_encrypted_memory_support_not_required(self, extra_specs,
image=None):
resreq = self._get_resource_request(extra_specs, image)
- expected = self._get_expected_resource_request(False)
+ expected = self._get_expected_resource_request(None)
self.assertResourceRequestsEqual(expected, resreq)
@@ -2442,9 +2448,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
@mock.patch.object(utils, 'LOG')
def _test_encrypted_memory_support_required(self, requesters, extra_specs,
- mock_log, image=None):
+ mock_log, image=None,
+ model='amd-sev'):
resreq = self._get_resource_request(extra_specs, image)
- expected = self._get_expected_resource_request(True)
+ expected = self._get_expected_resource_request(model)
self.assertResourceRequestsEqual(expected, resreq)
mock_log.debug.assert_has_calls([
@@ -2452,6 +2459,14 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
orc.MEM_ENCRYPTION_CONTEXT)
])
+ me_trait = 'HW_CPU_X86_AMD_SEV'
+ if model == 'amd-sev-es':
+ me_trait = 'HW_CPU_X86_AMD_SEV_ES'
+ mock_log.debug.assert_has_calls([
+ mock.call('Requiring memory encryption model %s via trait %s',
+ model, me_trait)
+ ])
+
def test_encrypted_memory_support_extra_spec(self):
for extra_spec in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
@@ -2494,6 +2509,79 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
hw_mem_encryption=image_prop))
)
+ def test_encrypted_memory_model_extra_spec(self):
+ for model in ('amd-sev', 'amd-sev-es'):
+ self._test_encrypted_memory_support_required(
+ 'hw:mem_encryption extra spec',
+ {'hw:mem_encryption': 'true',
+ 'hw:mem_encryption_model': model},
+ image=objects.ImageMeta(
+ id='005249be-3c2f-4351-9df7-29bb13c21b14',
+ properties=objects.ImageMetaProps(
+ hw_machine_type='q35',
+ hw_firmware_type='uefi')),
+ model=model
+ )
+
+ def test_encrypted_memory_model_image_prop(self):
+ for model in ('amd-sev', 'amd-sev-es'):
+ self._test_encrypted_memory_support_required(
+ 'hw_mem_encryption image property',
+ {},
+ image=objects.ImageMeta(
+ id='005249be-3c2f-4351-9df7-29bb13c21b14',
+ name=self.image_name,
+ properties=objects.ImageMetaProps(
+ hw_machine_type='q35',
+ hw_firmware_type='uefi',
+ hw_mem_encryption='true',
+ hw_mem_encryption_model=model)),
+ model=model
+ )
+
+ def test_encrypted_memory_model_both_required(self):
+ for model in ('amd-sev', 'amd-sev-es'):
+ self._test_encrypted_memory_support_required(
+ 'hw:mem_encryption extra spec and '
+ 'hw_mem_encryption image property',
+ {'hw:mem_encryption': 'true',
+ 'hw:mem_encryption_model': model},
+ image=objects.ImageMeta(
+ id='005249be-3c2f-4351-9df7-29bb13c21b14',
+ name=self.image_name,
+ properties=objects.ImageMetaProps(
+ hw_machine_type='q35',
+ hw_firmware_type='uefi',
+ hw_mem_encryption='true',
+ hw_mem_encryption_model=model)),
+ model=model
+ )
+
+ def test_encrypted_memory_model_conflict_1(self):
+ for f_model, i_model in (
+ ('amd-sev', 'amd-sev-es'),
+ ('amd-sev-es', 'amd-sev')
+ ):
+ image = objects.ImageMeta(
+ name=self.image_name,
+ properties=objects.ImageMetaProps(
+ hw_machine_type='q35',
+ hw_firmware_type='uefi',
+ hw_mem_encryption='true',
+ hw_mem_encryption_model=i_model
+ )
+ )
+ reqspec = self._get_request_spec(
+ extra_specs={
+ 'hw:mem_encryption': 'true',
+ 'hw:mem_encryption_model': f_model,
+ },
+ image=image)
+ self.assertRaises(
+ exception.FlavorImageConflict,
+ utils.ResourceRequest.from_request_spec, reqspec
+ )
+
class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
"""These test cases assert what happens when the group policy is missing
diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py
index 4dc138fd3e..1f3f5372ac 100644
--- a/nova/tests/unit/virt/libvirt/test_config.py
+++ b/nova/tests/unit/virt/libvirt/test_config.py
@@ -2716,6 +2716,17 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
self.assertXmlEqual(launch_security_expected, xml)
+ obj.policy = 0x0035
+ xml = obj.to_xml()
+ launch_security_expected = """
+
+ 0x0035
+ 47
+ 1
+ """
+
+ self.assertXmlEqual(launch_security_expected, xml)
+
def test_config_lxc(self):
obj = config.LibvirtConfigGuest()
obj.virt_type = "lxc"
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 9f3ce423f6..1c31ec5a01 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -3787,9 +3787,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
None, None, flavor, image_meta,
)
- def _test_sev_enabled(self, expected=None, host_sev_enabled=False,
- enc_extra_spec=None, enc_image_prop=None,
- hw_machine_type=None, hw_firmware_type=None):
+ def _test_get_mem_encryption_config(
+ self, expected=None, host_sev_enabled=False, enc_extra_spec=None,
+ enc_image_prop=None, hw_machine_type=None, hw_firmware_type=None):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
drvr._host._supports_amd_sev = host_sev_enabled
@@ -3812,42 +3812,45 @@ class LibvirtConnTestCase(test.NoDBTestCase,
{'id': '150d530b-1c57-4367-b754-1f1b5237923d'},
{}, image_props)
- enabled = drvr._sev_enabled(flavor, image_meta)
+ me_config = drvr._get_mem_encryption_config(flavor, image_meta)
if expected is None:
- self.fail("_test_sev_enabled called without an expected "
- "return value. Maybe you expected an exception?")
+ self.assertIsNone(me_config)
+ else:
+ self.assertEqual(expected, me_config)
- self.assertEqual(expected, enabled)
+ def test_get_mem_encryption_config_no_host_support(self):
+ self._test_get_mem_encryption_config()
- def test_sev_enabled_no_host_support(self):
- self._test_sev_enabled(False)
+ def test_get_mem_encryption_config_host_support_no_flavor_image(self):
+ self._test_get_mem_encryption_config(host_sev_enabled=True)
- def test_sev_enabled_host_support_no_flavor_image(self):
- self._test_sev_enabled(False, host_sev_enabled=True)
+ def test_get_mem_encryption_config_no_host_support_flavor_requested(self):
+ self._test_get_mem_encryption_config(enc_extra_spec=True)
- def test_sev_enabled_no_host_support_flavor_requested(self):
- self._test_sev_enabled(False, enc_extra_spec=True)
+ def test_get_mem_encryption_config_no_host_support_image_requested(self):
+ self._test_get_mem_encryption_config(enc_image_prop=True)
- def test_sev_enabled_no_host_support_image_requested(self):
- self._test_sev_enabled(False, enc_image_prop=True)
+ def test_get_mem_encryption_config_host_support_flavor_requested(self):
+ expected = hardware.MemEncryptionConfig(
+ model=fields.MemEncryptionModel.AMD_SEV)
+ self._test_get_mem_encryption_config(
+ expected, host_sev_enabled=True, enc_extra_spec=True,
+ hw_firmware_type='uefi', hw_machine_type='q35')
- def test_sev_enabled_host_support_flavor_requested(self):
- self._test_sev_enabled(True, host_sev_enabled=True,
- enc_extra_spec=True, hw_firmware_type='uefi',
- hw_machine_type='q35')
-
- def test_sev_enabled_host_support_image_requested(self):
- self._test_sev_enabled(True, host_sev_enabled=True,
- enc_image_prop=True, hw_firmware_type='uefi',
- hw_machine_type='q35')
+ def test_get_mem_encryption_config_host_support_image_requested(self):
+ expected = hardware.MemEncryptionConfig(
+ model=fields.MemEncryptionModel.AMD_SEV)
+ self._test_get_mem_encryption_config(
+ expected, host_sev_enabled=True, enc_image_prop=True,
+ hw_firmware_type='uefi', hw_machine_type='q35')
# The cases where the flavor and image requests contradict each other
# are already covered by test_hardware.MemEncryptionConflictTestCase
# so we don't need to test them in great detail here.
- def test_sev_enabled_host_extra_spec_image_conflict(self):
+ def test_get_mem_encryption_config_host_extra_spec_image_conflict(self):
exc = self.assertRaises(exception.FlavorImageConflict,
- self._test_sev_enabled,
+ self._test_get_mem_encryption_config,
host_sev_enabled=True, enc_extra_spec=False,
enc_image_prop=True)
self.assertEqual(
@@ -3855,9 +3858,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
"to False, conflicting with image fake_image which has "
"hw_mem_encryption property explicitly set to True", str(exc))
- def test_sev_enabled_host_extra_spec_no_uefi(self):
+ def test_get_mem_encryption_config_host_extra_spec_no_uefi(self):
exc = self.assertRaises(exception.FlavorImageConflict,
- self._test_sev_enabled,
+ self._test_get_mem_encryption_config,
host_sev_enabled=True, enc_extra_spec=True)
self.assertEqual(
"Memory encryption requested by hw:mem_encryption extra spec in "
@@ -3865,9 +3868,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
"'hw_firmware_type' property set to 'uefi' or volume-backed "
"instance was requested", str(exc))
- def test_sev_enabled_host_extra_spec_no_machine_type(self):
+ def test_get_mem_encryption_config_host_extra_spec_no_machine_type(self):
exc = self.assertRaises(exception.InvalidMachineType,
- self._test_sev_enabled,
+ self._test_get_mem_encryption_config,
host_sev_enabled=True, enc_extra_spec=True,
hw_firmware_type='uefi')
self.assertEqual(
@@ -3875,9 +3878,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
"(150d530b-1c57-4367-b754-1f1b5237923d): q35 type is required "
"for SEV to work", str(exc))
- def test_sev_enabled_host_extra_spec_pc(self):
+ def test_get_mem_encryption_config_host_extra_spec_pc(self):
exc = self.assertRaises(exception.InvalidMachineType,
- self._test_sev_enabled,
+ self._test_get_mem_encryption_config,
host_sev_enabled=True, enc_extra_spec=True,
hw_firmware_type='uefi', hw_machine_type='pc')
self.assertEqual(
@@ -3920,7 +3923,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(47, feature.cbitpos)
self.assertEqual(1, feature.reduced_phys_bits)
- def _setup_sev_guest(self, extra_image_properties=None):
+ def _setup_sev_guest(self, extra_image_properties=None, model=None):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
drvr._host._supports_uefi = True
drvr._host._supports_amd_sev = True
@@ -3933,6 +3936,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
extra_specs = {
"hw:mem_encryption": True,
}
+ if model:
+ extra_specs['hw:mem_encryption_model'] = model
flavor = objects.Flavor(
id=42, name='m1.small', memory_mb=6,
vcpus=28, root_gb=496, ephemeral_gb=8128,
@@ -3960,15 +3965,23 @@ class LibvirtConnTestCase(test.NoDBTestCase,
image_meta, disk_info,
context=ctxt)
- def test_get_guest_config_sev_no_feature(self):
+ @ddt.data(None, 'amd-sev', 'amd-sev-es')
+ def test_get_guest_config_sev_no_feature(self, sev_model):
self.assertRaises(exception.MissingDomainCapabilityFeatureException,
- self._setup_sev_guest)
+ self._setup_sev_guest, model=sev_model)
+ @ddt.unpack
+ @ddt.data(
+ {'sev_model': None, 'sev_policy': 0x0033},
+ {'sev_model': 'amd-sev', 'sev_policy': 0x0033},
+ {'sev_model': 'amd-sev-es', 'sev_policy': 0x0035}
+ )
@mock.patch.object(host.Host, 'get_domain_capabilities')
@mock.patch.object(designer, 'set_driver_iommu_for_all_devices')
- def test_get_guest_config_sev(self, mock_designer, fake_domain_caps):
+ def test_get_guest_config_sev(self, mock_designer, fake_domain_caps,
+ sev_model, sev_policy):
self._setup_fake_domain_caps(fake_domain_caps)
- cfg = self._setup_sev_guest()
+ cfg = self._setup_sev_guest(model=sev_model)
# SEV-related tag should be set
self.assertIsInstance(cfg.launch_security,
@@ -3976,6 +3989,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertIsInstance(cfg.membacking,
vconfig.LibvirtConfigGuestMemoryBacking)
self.assertTrue(cfg.membacking.locked)
+ self.assertEqual(sev_policy, cfg.launch_security.policy)
mock_designer.assert_called_once_with(cfg)
@@ -7359,14 +7373,20 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[6].type, "unix")
self.assertEqual(cfg.devices[6].target_name, "org.qemu.guest_agent.0")
+ @ddt.unpack
+ @ddt.data(
+ {'sev_model': None},
+ {'sev_model': 'amd-sev'},
+ {'sev_model': 'amd-sev-es'}
+ )
@mock.patch.object(host.Host, 'get_domain_capabilities')
@mock.patch.object(designer, 'set_driver_iommu_for_all_devices')
def test_get_guest_config_with_qga_through_image_meta_with_sev(
- self, mock_designer, fake_domain_caps,
+ self, mock_designer, fake_domain_caps, sev_model
):
self._setup_fake_domain_caps(fake_domain_caps)
extra_properties = {"hw_qemu_guest_agent": "yes"}
- cfg = self._setup_sev_guest(extra_properties)
+ cfg = self._setup_sev_guest(extra_properties, model=sev_model)
self.assertIsInstance(cfg.devices[8],
vconfig.LibvirtConfigGuestController)
@@ -10149,8 +10169,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
return fake_config
- @mock.patch.object(libvirt_driver.LibvirtDriver, '_sev_enabled',
- new=mock.Mock(return_value=False))
+ @mock.patch.object(libvirt_driver.LibvirtDriver,
+ '_get_mem_encryption_config',
+ new=mock.Mock(return_value=None))
@mock.patch.object(volume_drivers.LibvirtFakeVolumeDriver, 'get_config')
@mock.patch.object(libvirt_driver.LibvirtDriver, '_set_cache_mode')
def test_get_volume_config(self, mock_set_cache_mode, mock_get_config):
@@ -10177,8 +10198,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_set_cache_mode.assert_called_once_with(returned_config)
self.assertEqual(generated_config.to_xml(), returned_config.to_xml())
- @mock.patch.object(libvirt_driver.LibvirtDriver, '_sev_enabled',
- new=mock.Mock(return_value=True))
+ @mock.patch.object(
+ libvirt_driver.LibvirtDriver, '_get_mem_encryption_config',
+ new=mock.Mock(return_value=hardware.MemEncryptionConfig(
+ model=fields.MemEncryptionModel.AMD_SEV)))
@mock.patch.object(libvirt_driver.LibvirtDriver, '_set_cache_mode',
new=mock.Mock())
@mock.patch.object(volume_drivers.LibvirtFakeVolumeDriver, 'get_config')
@@ -13547,7 +13570,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
guest, 'get_xml_desc', return_value=initial_xml
),
mock.patch.object(
- drvr, '_sev_enabled', new=mock.Mock(return_value=False)
+ drvr, '_get_mem_encryption_config',
+ new=mock.Mock(return_value=None)
)
):
config = libvirt_migrate.get_updated_guest_xml(
@@ -13755,7 +13779,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
guest, 'get_xml_desc', return_value=initial_xml
),
mock.patch.object(
- drvr, '_sev_enabled', new=mock.Mock(return_value=False)
+ drvr, '_get_mem_encryption_config',
+ new=mock.Mock(return_value=None)
)
):
config = libvirt_migrate.get_updated_guest_xml(
@@ -13801,7 +13826,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
guest, 'get_xml_desc', return_value=initial_xml
),
mock.patch.object(
- drvr, '_sev_enabled', new=mock.Mock(return_value=False)
+ drvr, '_get_mem_encryption_config',
+ new=mock.Mock(return_value=None)
)
):
config = libvirt_migrate.get_updated_guest_xml(
@@ -25775,7 +25801,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
mock_set_metadata.assert_called_once_with(config_meta)
@mock.patch('nova.virt.libvirt.designer.set_driver_iommu_for_device')
- @mock.patch.object(libvirt_driver.LibvirtDriver, '_sev_enabled')
+ @mock.patch.object(libvirt_driver.LibvirtDriver,
+ '_get_mem_encryption_config')
@mock.patch.object(objects.Instance, 'get_network_info')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(libvirt_driver.LibvirtDriver, '_build_device_metadata')
@@ -25785,8 +25812,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
def _test_attach_interface(self, power_state, expected_flags,
mock_get_domain, mock_attach, mock_info,
mock_build, mock_save, mock_get_network_info,
- mock_sev_enabled, mock_designer_set_iommu,
- sev_enabled=False):
+ mock_me_config, mock_designer_set_iommu,
+ me_config=None):
instance = self._create_instance()
network_info = _fake_network_info(self)
domain = FakeVirtDomain(fake_xml="""
@@ -25804,7 +25831,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
""")
mock_get_domain.return_value = domain
mock_info.return_value = [power_state, 1, 2, 3, 4]
- mock_sev_enabled.return_value = sev_enabled
+ mock_me_config.return_value = me_config
fake_image_meta = objects.ImageMeta.from_dict(
{'id': instance.image_ref})
@@ -25830,7 +25857,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
mock_get_network_info.assert_called_once_with()
mock_attach.assert_called_once_with(expected.to_xml(),
flags=expected_flags)
- if sev_enabled:
+ if me_config:
mock_designer_set_iommu.assert_called_once_with(expected)
def test_attach_interface_with_running_instance(self):
@@ -25844,7 +25871,9 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
power_state.RUNNING,
(fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG |
fakelibvirt.VIR_DOMAIN_AFFECT_LIVE),
- sev_enabled=True)
+ me_config=hardware.MemEncryptionConfig(
+ model=fields.MemEncryptionModel.AMD_SEV
+ ))
def test_attach_interface_with_pause_instance(self):
self._test_attach_interface(
diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py
index 23a3ff010d..de7787faff 100644
--- a/nova/tests/unit/virt/test_hardware.py
+++ b/nova/tests/unit/virt/test_hardware.py
@@ -5333,7 +5333,7 @@ class MemEncryptionNotRequiredTestCase(test.NoDBTestCase):
if image_meta is None:
image_meta = objects.ImageMeta(properties=objects.ImageMetaProps())
- self.assertFalse(hw.get_mem_encryption_constraint(flavor, image_meta))
+ self.assertIsNone(hw.get_mem_encryption_constraint(flavor, image_meta))
def test_requirement_disabled(self):
self._test_encrypted_memory_support_not_required()
@@ -5509,8 +5509,8 @@ class MemEncryptionRequestedWithInvalidMachineTypeTestCase(
flavor = objects.Flavor(name=self.flavor_name,
extra_specs=extra_specs)
exc = self.assertRaises(self.expected_exception,
- hw.get_mem_encryption_constraint,
- flavor, image_meta)
+ hw.get_mem_encryption_constraint,
+ flavor, image_meta)
self.assertEqual(self.expected_error % error_data, str(exc))
def test_flavor_requires_encrypted_memory_support_pc(self):
@@ -5571,8 +5571,10 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
'id': self.image_id,
'name': self.image_name,
'properties': image_props})
- self.assertTrue(hw.get_mem_encryption_constraint(flavor,
- image_meta))
+ expected = hw.MemEncryptionConfig(
+ model=fields.MemEncryptionModel.AMD_SEV)
+ self.assertEqual(
+ expected, hw.get_mem_encryption_constraint(flavor, image_meta))
mock_log.debug.assert_has_calls([
mock.call("Memory encryption requested by %s", requesters)
])
@@ -5643,8 +5645,11 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
'size': 0,
'status': 'active'})
- self.assertTrue(hw.get_mem_encryption_constraint(flavor,
- image_meta))
+ expected = hw.MemEncryptionConfig(
+ model=fields.MemEncryptionModel.AMD_SEV)
+ self.assertEqual(
+ expected,
+ hw.get_mem_encryption_constraint(flavor, image_meta))
requesters = "hw:mem_encryption extra spec in %s flavor and " \
"hw_mem_encryption property of image %s" % \
diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py
index 2dc1b0efa2..65ffce16ca 100644
--- a/nova/virt/hardware.py
+++ b/nova/virt/hardware.py
@@ -48,6 +48,10 @@ class VTPMConfig(ty.NamedTuple):
model: str
+class MemEncryptionConfig(ty.NamedTuple):
+ model: str
+
+
def get_vcpu_pin_set():
"""Parse ``vcpu_pin_set`` config.
@@ -1157,10 +1161,9 @@ def get_mem_encryption_constraint(
flavor: 'objects.Flavor',
image_meta: 'objects.ImageMeta',
machine_type: ty.Optional[str] = None,
-) -> bool:
- """Return a boolean indicating whether encryption of guest memory was
- requested, either via the hw:mem_encryption extra spec or the
- hw_mem_encryption image property (or both).
+) -> ty.Optional[MemEncryptionConfig]:
+ """Return memory encryption context requested either via flavor extra specs
+ or image properties (or both).
Also watch out for contradictory requests between the flavor and
image regarding memory encryption, and raise an exception where
@@ -1188,8 +1191,7 @@ def get_mem_encryption_constraint(
:param machine_type: a string representing the machine type (optional)
:raises: nova.exception.FlavorImageConflict
:raises: nova.exception.InvalidMachineType
- :returns: boolean indicating whether encryption of guest memory
- was requested
+ :returns: A named tuple containing the memory encryption model, else None.
"""
flavor_mem_enc_str, image_mem_enc = _get_flavor_image_meta(
@@ -1203,7 +1205,7 @@ def get_mem_encryption_constraint(
# boolean is handled automatically
if not flavor_mem_enc and not image_mem_enc:
- return False
+ return None
_check_for_mem_encryption_requirement_conflicts(
flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta)
@@ -1215,20 +1217,53 @@ def get_mem_encryption_constraint(
# encryption enabled, image_meta has no id key. See bug #2041511.
# So we check whether id exists. If there is no value, we set it
# to a sentinel value we can detect later. i.e. ''.
- requesters = []
+ enc_requesters = []
if flavor_mem_enc:
- requesters.append("hw:mem_encryption extra spec in %s flavor" %
- flavor.name)
+ enc_requesters.append("hw:mem_encryption extra spec in %s flavor" %
+ flavor.name)
if image_mem_enc:
image_id = (image_meta.id if 'id' in image_meta else '')
- requesters.append("hw_mem_encryption property of image %s" %
- image_id)
+ enc_requesters.append("hw_mem_encryption property of image %s" %
+ image_id)
- _check_mem_encryption_uses_uefi_image(requesters, image_meta)
+ _check_mem_encryption_uses_uefi_image(enc_requesters, image_meta)
_check_mem_encryption_machine_type(image_meta, machine_type)
- LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
- return True
+ LOG.debug("Memory encryption requested by %s",
+ " and ".join(enc_requesters))
+
+ flavor_mem_enc_model, image_mem_enc_model = _get_flavor_image_meta(
+ 'mem_encryption_model', flavor, image_meta)
+ _check_for_mem_encryption_model_conflicts(
+ flavor_mem_enc_model, image_mem_enc_model, flavor, image_meta)
+
+ mem_enc_model = None
+ model_requesters = []
+ if flavor_mem_enc_model:
+ mem_enc_model = flavor_mem_enc_model
+ model_requesters.append(
+ "hw:mem_encryption_model extra spec in %s flavor" % flavor.name)
+ if image_mem_enc_model:
+ mem_enc_model = image_mem_enc_model
+ image_id = (image_meta.id if 'id' in image_meta else '')
+ model_requesters.append(
+ "hw_mem_encryption_model property of image %s" % image_id)
+
+ if not mem_enc_model:
+ return MemEncryptionConfig(model=fields.MemEncryptionModel.AMD_SEV)
+
+ LOG.debug("Memory encryption model requested by %s",
+ " and ".join(model_requesters))
+
+ if mem_enc_model not in fields.MemEncryptionModel.ALL:
+ raise exception.Invalid(
+ ("Invalid memory encryption model %(model)r. "
+ "Allowed values: %(valid)s.") %
+ {'model': mem_enc_model,
+ 'valid': ', '.join(fields.MemEncryptionModel.ALL)}
+ )
+
+ return MemEncryptionConfig(model=mem_enc_model)
def _check_for_mem_encryption_requirement_conflicts(
@@ -1255,6 +1290,31 @@ def _check_for_mem_encryption_requirement_conflicts(
raise exception.FlavorImageConflict(emsg % data)
+def _check_for_mem_encryption_model_conflicts(
+ flavor_mem_enc_model, image_mem_enc_model, flavor, image_meta):
+ # Check for conflicts between explicit requirements regarding
+ # memory encryption model.
+ if (flavor_mem_enc_model is not None and
+ image_mem_enc_model is not None and
+ flavor_mem_enc_model != image_mem_enc_model):
+ emsg = _(
+ "Flavor %(flavor_name)s has hw:mem_encryption_model extra "
+ "spec explicitly set to %(flavor_val)s, conflicting with "
+ "image %(image_name)s which has hw_mem_encryption_model property "
+ "explicitly set to %(image_val)s"
+ )
+ # image_meta.name is not set if image object represents root
+ # Cinder volume.
+ image_name = (image_meta.name if 'name' in image_meta else None)
+ data = {
+ 'flavor_name': flavor.name,
+ 'flavor_val': flavor_mem_enc_model,
+ 'image_name': image_name,
+ 'image_val': image_mem_enc_model,
+ }
+ raise exception.FlavorImageConflict(emsg % data)
+
+
def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
if image_meta.properties.get('hw_firmware_type') == 'uefi':
return
diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py
index 8a62923313..512011cee8 100644
--- a/nova/virt/libvirt/config.py
+++ b/nova/virt/libvirt/config.py
@@ -3023,6 +3023,8 @@ class LibvirtConfigGuestSEVLaunchSecurity(LibvirtConfigObject):
super(LibvirtConfigGuestSEVLaunchSecurity, self).__init__(
root_name='launchSecurity', **kwargs)
+ # hardcoded default for SEV according to the spec
+ self.policy = 0x0033
self.cbitpos = None
self.reduced_phys_bits = None
@@ -3031,7 +3033,7 @@ class LibvirtConfigGuestSEVLaunchSecurity(LibvirtConfigObject):
root.set('type', 'sev')
policy = etree.Element('policy')
- policy.text = '0x0033' # hardcoded default according to the spec
+ policy.text = '0x%04x' % self.policy
root.append(policy)
cbitpos = etree.Element('cbitpos')
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index f63586ea82..29b32433d8 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -2133,7 +2133,8 @@ class LibvirtDriver(driver.ComputeDriver):
vol_driver = self._get_volume_driver(connection_info)
conf = vol_driver.get_config(connection_info, disk_info)
- if self._sev_enabled(instance.flavor, instance.image_meta):
+ if self._get_mem_encryption_config(
+ instance.flavor, instance.image_meta):
designer.set_driver_iommu_for_device(conf)
self._set_cache_mode(conf)
@@ -3007,7 +3008,7 @@ class LibvirtDriver(driver.ComputeDriver):
instance.flavor,
CONF.libvirt.virt_type)
- if self._sev_enabled(instance.flavor, image_meta):
+ if self._get_mem_encryption_config(instance.flavor, image_meta):
designer.set_driver_iommu_for_device(cfg)
try:
@@ -6872,7 +6873,7 @@ class LibvirtDriver(driver.ComputeDriver):
# of AMD SEV, any virtio device should use iommu driver, and
# libvirt does not know about it. That is why the controller
# should be created manually.
- if self._sev_enabled(flavor, image_meta):
+ if self._get_mem_encryption_config(flavor, image_meta):
self._add_virtio_serial_controller(guest, instance)
LOG.debug("Qemu guest agent is enabled through image "
@@ -6935,7 +6936,7 @@ class LibvirtDriver(driver.ComputeDriver):
membacking.sharedaccess = True
membacking.allocateimmediate = True
membacking.discard = True
- if self._sev_enabled(flavor, image_meta):
+ if self._get_mem_encryption_config(flavor, image_meta):
if not membacking:
membacking = vconfig.LibvirtConfigGuestMemoryBacking()
membacking.locked = True
@@ -7526,7 +7527,7 @@ class LibvirtDriver(driver.ComputeDriver):
self._get_guest_os_type()
)
- sev_enabled = self._sev_enabled(flavor, image_meta)
+ me_config = self._get_mem_encryption_config(flavor, image_meta)
self._configure_guest_by_virt_type(guest, instance, image_meta, flavor)
if CONF.libvirt.virt_type != 'lxc':
@@ -7604,10 +7605,11 @@ class LibvirtDriver(driver.ComputeDriver):
if mdevs:
self._guest_add_mdevs(guest, mdevs)
- if sev_enabled:
+ if me_config:
caps = self._host.get_capabilities()
- self._guest_configure_sev(guest, caps.host.cpu.arch,
- guest.os_mach_type)
+ self._guest_configure_mem_encryption(guest, caps.host.cpu.arch,
+ guest.os_mach_type,
+ me_config.model)
if vpmems:
self._guest_add_vpmems(guest, vpmems)
@@ -7649,7 +7651,7 @@ class LibvirtDriver(driver.ComputeDriver):
guest.max_memory_slots += 1
guest.add_device(vpmem_config)
- def _sev_enabled(self, flavor, image_meta):
+ def _get_mem_encryption_config(self, flavor, image_meta):
"""To enable AMD SEV, the following should be true:
a) the supports_amd_sev instance variable in the host is
@@ -7671,13 +7673,13 @@ class LibvirtDriver(driver.ComputeDriver):
are run while determining whether SEV is selected.
"""
if not self._host.supports_amd_sev:
- return False
+ return None
mach_type = libvirt_utils.get_machine_type(image_meta)
return hardware.get_mem_encryption_constraint(flavor, image_meta,
mach_type)
- def _guest_configure_sev(self, guest, arch, mach_type):
+ def _guest_configure_mem_encryption(self, guest, arch, mach_type, model):
sev = self._find_sev_feature(arch, mach_type)
if sev is None:
# In theory this should never happen because it should
@@ -7694,12 +7696,14 @@ class LibvirtDriver(driver.ComputeDriver):
feature='sev')
designer.set_driver_iommu_for_all_devices(guest)
- self._guest_add_launch_security(guest, sev)
+ self._guest_add_sev_launch_security(guest, sev, model)
- def _guest_add_launch_security(self, guest, sev):
+ def _guest_add_sev_launch_security(self, guest, sev, model):
launch_security = vconfig.LibvirtConfigGuestSEVLaunchSecurity()
launch_security.cbitpos = sev.cbitpos
launch_security.reduced_phys_bits = sev.reduced_phys_bits
+ if model == fields.MemEncryptionModel.AMD_SEV_ES:
+ launch_security.policy = 0x0035
guest.launch_security = launch_security
def _find_sev_feature(self, arch, mach_type):
diff --git a/releasenotes/notes/bp-amd-sev-es-libvirt-support-089ec0e394156d0a.yaml b/releasenotes/notes/bp-amd-sev-es-libvirt-support-089ec0e394156d0a.yaml
new file mode 100644
index 0000000000..376f17791a
--- /dev/null
+++ b/releasenotes/notes/bp-amd-sev-es-libvirt-support-089ec0e394156d0a.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - |
+ The libvirt driver can now support requests for guest RAM to be encrypted
+ using the AMD SEV-ES(Secure Encrypted Virtualization-Encrypted State),
+ instead of AMD SEV.
+
+ Usage of AMD SEV-ES for memory encryption can be required either via
+ a flavor which has the ``hw:mem_encryption_model`` extra spec set to
+ ``amd-sev-es``, or via an image which has the ``hw_mem_encryption_model``
+ property set to ``amd-sev-es``. In case the extra spec and the property are
+ unset or set to ``amd-sev``, then AMD SEV is used for memory encryption.