Merge "libvirt: Launch instances with SEV-ES memory encryption"

This commit is contained in:
Zuul
2025-08-28 23:24:30 +00:00
committed by Gerrit Code Review
12 changed files with 344 additions and 112 deletions
+7 -11
View File
@@ -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
+16
View File
@@ -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=(
+14 -6
View File
@@ -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.
@@ -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',
)
+93 -5
View File
@@ -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
@@ -2716,6 +2716,17 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
self.assertXmlEqual(launch_security_expected, xml)
obj.policy = 0x0035
xml = obj.to_xml()
launch_security_expected = """
<launchSecurity type="sev">
<policy>0x0035</policy>
<cbitpos>47</cbitpos>
<reducedPhysBits>1</reducedPhysBits>
</launchSecurity>"""
self.assertXmlEqual(launch_security_expected, xml)
def test_config_lxc(self):
obj = config.LibvirtConfigGuest()
obj.virt_type = "lxc"
+81 -52
View File
@@ -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):
</domain>""")
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(
+12 -7
View File
@@ -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" % \
+75 -15
View File
@@ -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. '<no-id>'.
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 '<no-id>')
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 '<no-id>')
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
+3 -1
View File
@@ -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')
+17 -13
View File
@@ -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):
@@ -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.