Merge "Ensure non-q35 machine type is not used when booting with SEV"

This commit is contained in:
Zuul 2019-09-07 08:54:50 +00:00 committed by Gerrit Code Review
commit 0a8444e903
6 changed files with 154 additions and 33 deletions

View File

@ -75,6 +75,7 @@ INVALID_FLAVOR_IMAGE_EXCEPTIONS = (
exception.InvalidCPUAllocationPolicy,
exception.InvalidCPUThreadAllocationPolicy,
exception.InvalidEmulatorThreadsPolicy,
exception.InvalidMachineType,
exception.InvalidNUMANodesNumber,
exception.InvalidRequest,
exception.MemoryPageSizeForbidden,

View File

@ -1977,6 +1977,11 @@ class InvalidHypervisorVirtType(Invalid):
"recognised")
class InvalidMachineType(Invalid):
msg_fmt = _("Machine type '%(mtype)s' is not compatible with image "
"%(image_name)s (%(image_id)s): %(reason)s")
class InvalidVirtualMachineMode(Invalid):
msg_fmt = _("Virtual machine mode '%(vmmode)s' is not recognised")

View File

@ -6140,6 +6140,15 @@ class ServersControllerCreateTest(test.TestCase):
self.controller.create,
self.req, body=self.body)
@mock.patch('nova.virt.hardware.get_mem_encryption_constraint',
side_effect=exception.InvalidMachineType(
message="fake conflict reason"))
def test_create_instance_raise_invalid_machine_type(
self, mock_conflict):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create,
self.req, body=self.body)
@mock.patch('nova.virt.hardware.numa_get_constraints',
side_effect=exception.ImageCPUPinningForbidden())
def test_create_instance_raise_image_cpu_pinning_forbidden(

View File

@ -1317,7 +1317,9 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
'hw:mem_encryption extra spec',
{'hw:mem_encryption': extra_spec},
image=objects.ImageMeta(
id='005249be-3c2f-4351-9df7-29bb13c21b14',
properties=objects.ImageMetaProps(
hw_machine_type='q35',
hw_firmware_type='uefi'))
)
@ -1327,8 +1329,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
'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=image_prop))
)
@ -1341,8 +1345,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
'hw_mem_encryption image property',
{'hw:mem_encryption': extra_spec},
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=image_prop))
)

View File

@ -3798,32 +3798,50 @@ class MemEncryptionFlavorImageConflictTestCase(test.NoDBTestCase):
)
class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
class MemEncryptionRequestedInvalidImagePropsTestCase(test.NoDBTestCase):
flavor_name = 'm1.faketiny'
image_name = 'fakecirros'
image_id = '7ec4448e-f3fd-44b1-b172-9a7980f0f29f'
def _test_encrypted_memory_support_no_uefi(self, extra_spec, image_prop,
requesters):
def _test_encrypted_memory_support_raises(self, enc_extra_spec,
enc_image_prop, image_props,
error_data):
extra_specs = {}
if extra_spec:
extra_specs['hw:mem_encryption'] = extra_spec
if enc_extra_spec:
extra_specs['hw:mem_encryption'] = enc_extra_spec
flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
if enc_image_prop:
image_props['hw_mem_encryption'] = enc_image_prop
image_meta = fake_image_obj(
{'name': self.image_name}, {'hw_firmware_type': 'bios'},
{'hw_mem_encryption': True} if image_prop else {})
error = (
"Memory encryption requested by %(requesters)s but image "
"%(image_name)s doesn't have 'hw_firmware_type' property "
"set to 'uefi'"
)
exc = self.assertRaises(
exception.FlavorImageConflict,
hw.get_mem_encryption_constraint,
flavor, image_meta
)
{'id': self.image_id, 'name': self.image_name},
{}, image_props)
exc = self.assertRaises(self.expected_exception,
hw.get_mem_encryption_constraint,
flavor, image_meta)
self.assertEqual(self.expected_error % error_data, str(exc))
class MemEncryptionRequestedWithoutUEFITestCase(
MemEncryptionRequestedInvalidImagePropsTestCase):
expected_exception = exception.FlavorImageConflict
expected_error = (
"Memory encryption requested by %(requesters)s but image "
"%(image_name)s doesn't have 'hw_firmware_type' property "
"set to 'uefi'"
)
def _test_encrypted_memory_support_no_uefi(self, enc_extra_spec,
enc_image_prop, requesters):
error_data = {'requesters': requesters,
'image_name': self.image_name}
self.assertEqual(error % error_data, str(exc))
for image_props in ({},
{'hw_machine_type': 'q35'},
{'hw_firmware_type': 'bios'},
{'hw_machine_type': 'q35',
'hw_firmware_type': 'bios'}):
self._test_encrypted_memory_support_raises(enc_extra_spec,
enc_image_prop,
image_props, error_data)
def test_flavor_requires_encrypted_memory_support_no_uefi(self):
for extra_spec in ('1', 'true', 'True'):
@ -3847,28 +3865,73 @@ class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
% (self.flavor_name, self.image_name))
class MemEncryptionRequestedWithInvalidMachineTypeTestCase(
MemEncryptionRequestedInvalidImagePropsTestCase):
expected_exception = exception.InvalidMachineType
expected_error = (
"Machine type '%(mtype)s' is not compatible with image %(image_name)s "
"(%(image_id)s): q35 type is required for SEV to work")
def _test_encrypted_memory_support_pc(self, enc_extra_spec,
enc_image_prop):
error_data = {'image_id': self.image_id,
'image_name': self.image_name,
'mtype': 'pc'}
image_props = {'hw_firmware_type': 'uefi',
'hw_machine_type': 'pc'}
self._test_encrypted_memory_support_raises(enc_extra_spec,
enc_image_prop,
image_props, error_data)
def test_flavor_requires_encrypted_memory_support_pc(self):
for extra_spec in ('1', 'true', 'True'):
self._test_encrypted_memory_support_pc(extra_spec, None)
def test_image_requires_encrypted_memory_support_pc(self):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_pc(None, image_prop)
def test_flavor_image_require_encrypted_memory_support_pc(self):
for extra_spec in ('1', 'true', 'True'):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_pc(
extra_spec, image_prop)
class MemEncryptionRequiredTestCase(test.NoDBTestCase):
flavor_name = "m1.faketiny"
image_name = 'fakecirros'
image_id = '8a71a380-be23-47eb-9e72-9998586a0268'
@mock.patch.object(hw, 'LOG')
def _test_encrypted_memory_support_required(self, extra_specs,
image_props,
requesters, mock_log):
flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
image_meta = objects.ImageMeta(name=self.image_name,
properties=image_props)
image_props['hw_firmware_type'] = 'uefi'
self.assertTrue(hw.get_mem_encryption_constraint(flavor, image_meta))
mock_log.debug.assert_has_calls([
mock.call("Memory encryption requested by %s", requesters)
])
def _test_get_mem_encryption_constraint():
flavor = objects.Flavor(name=self.flavor_name,
extra_specs=extra_specs)
image_meta = objects.ImageMeta.from_dict({
'id': self.image_id,
'name': self.image_name,
'properties': image_props})
self.assertTrue(hw.get_mem_encryption_constraint(flavor,
image_meta))
mock_log.debug.assert_has_calls([
mock.call("Memory encryption requested by %s", requesters)
])
_test_get_mem_encryption_constraint()
for mtype in ('q35', 'pc-q35-2.1'):
image_props['hw_machine_type'] = mtype
_test_get_mem_encryption_constraint()
def test_require_encrypted_memory_support_extra_spec(self):
for extra_spec in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
{'hw:mem_encryption': extra_spec},
objects.ImageMetaProps(hw_firmware_type='uefi'),
{},
"hw:mem_encryption extra spec in %s flavor" % self.flavor_name
)
@ -3876,9 +3939,7 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
{},
objects.ImageMetaProps(
hw_mem_encryption=image_prop,
hw_firmware_type='uefi'),
{'hw_mem_encryption': image_prop},
"hw_mem_encryption property of image %s" % self.image_name
)
@ -3887,9 +3948,7 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
for image_prop in ('1', 'true', 'True'):
self._test_encrypted_memory_support_required(
{'hw:mem_encryption': extra_spec},
objects.ImageMetaProps(
hw_mem_encryption=image_prop,
hw_firmware_type='uefi'),
{'hw_mem_encryption': image_prop},
"hw:mem_encryption extra spec in %s flavor and "
"hw_mem_encryption property of image %s" %
(self.flavor_name, self.image_name)

View File

@ -1153,9 +1153,17 @@ def get_mem_encryption_constraint(flavor, image_meta):
2) the flavor and/or image request memory encryption, but the
image is missing hw_firmware_type=uefi
3) the flavor and/or image request memory encryption, but the
machine type is set to a value which does not contain 'q35'
This is called from the API layer, so get_machine_type() cannot be
called since it relies on being run from the compute node in order
to retrieve CONF.libvirt.hw_machine_type.
:param instance_type: Flavor object
:param image: an ImageMeta object
:raises: nova.exception.FlavorImageConflict
:raises: nova.exception.InvalidMachineType
:returns: boolean indicating whether encryption of guest memory
was requested
"""
@ -1188,6 +1196,7 @@ def get_mem_encryption_constraint(flavor, image_meta):
image_meta.name)
_check_mem_encryption_uses_uefi_image(requesters, image_meta)
_check_mem_encryption_machine_type(image_meta)
LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
return True
@ -1215,7 +1224,7 @@ def _check_for_mem_encryption_requirement_conflicts(
def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
if image_meta.properties.hw_firmware_type == 'uefi':
if image_meta.properties.get('hw_firmware_type') == 'uefi':
return
emsg = _(
@ -1227,6 +1236,38 @@ def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
raise exception.FlavorImageConflict(emsg % data)
def _check_mem_encryption_machine_type(image_meta):
# NOTE(aspiers): As explained in the SEV spec, SEV needs a q35
# machine type in order to bind all the virtio devices to the PCIe
# bridge so that they use virtio 1.0 and not virtio 0.9, since
# QEMU's iommu_platform feature was added in virtio 1.0 only:
#
# http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html
#
# So if the image explicitly requests a machine type which is not
# in the q35 family, raise an exception.
#
# Note that this check occurs at API-level, therefore we can't
# check here what value of CONF.libvirt.hw_machine_type may have
# been configured on the compute node.
mach_type = image_meta.properties.get('hw_machine_type')
# If hw_machine_type is not specified on the image and is not
# configured correctly on SEV compute nodes, then a separate check
# in the driver will catch that and potentially retry on other
# compute nodes.
if mach_type is None:
return
# Could be something like pc-q35-2.11 if a specific version of the
# machine type is required, so do substring matching.
if 'q35' not in mach_type:
raise exception.InvalidMachineType(
mtype=mach_type,
image_id=image_meta.id, image_name=image_meta.name,
reason=_("q35 type is required for SEV to work"))
def _get_numa_pagesize_constraint(flavor, image_meta):
"""Return the requested memory page size