Merge "Ensure non-q35 machine type is not used when booting with SEV"
This commit is contained in:
commit
0a8444e903
|
@ -75,6 +75,7 @@ INVALID_FLAVOR_IMAGE_EXCEPTIONS = (
|
||||||
exception.InvalidCPUAllocationPolicy,
|
exception.InvalidCPUAllocationPolicy,
|
||||||
exception.InvalidCPUThreadAllocationPolicy,
|
exception.InvalidCPUThreadAllocationPolicy,
|
||||||
exception.InvalidEmulatorThreadsPolicy,
|
exception.InvalidEmulatorThreadsPolicy,
|
||||||
|
exception.InvalidMachineType,
|
||||||
exception.InvalidNUMANodesNumber,
|
exception.InvalidNUMANodesNumber,
|
||||||
exception.InvalidRequest,
|
exception.InvalidRequest,
|
||||||
exception.MemoryPageSizeForbidden,
|
exception.MemoryPageSizeForbidden,
|
||||||
|
|
|
@ -1977,6 +1977,11 @@ class InvalidHypervisorVirtType(Invalid):
|
||||||
"recognised")
|
"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):
|
class InvalidVirtualMachineMode(Invalid):
|
||||||
msg_fmt = _("Virtual machine mode '%(vmmode)s' is not recognised")
|
msg_fmt = _("Virtual machine mode '%(vmmode)s' is not recognised")
|
||||||
|
|
||||||
|
|
|
@ -6140,6 +6140,15 @@ class ServersControllerCreateTest(test.TestCase):
|
||||||
self.controller.create,
|
self.controller.create,
|
||||||
self.req, body=self.body)
|
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',
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
||||||
side_effect=exception.ImageCPUPinningForbidden())
|
side_effect=exception.ImageCPUPinningForbidden())
|
||||||
def test_create_instance_raise_image_cpu_pinning_forbidden(
|
def test_create_instance_raise_image_cpu_pinning_forbidden(
|
||||||
|
|
|
@ -1317,7 +1317,9 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||||
'hw:mem_encryption extra spec',
|
'hw:mem_encryption extra spec',
|
||||||
{'hw:mem_encryption': extra_spec},
|
{'hw:mem_encryption': extra_spec},
|
||||||
image=objects.ImageMeta(
|
image=objects.ImageMeta(
|
||||||
|
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
||||||
properties=objects.ImageMetaProps(
|
properties=objects.ImageMetaProps(
|
||||||
|
hw_machine_type='q35',
|
||||||
hw_firmware_type='uefi'))
|
hw_firmware_type='uefi'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1327,8 +1329,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||||
'hw_mem_encryption image property',
|
'hw_mem_encryption image property',
|
||||||
{},
|
{},
|
||||||
image=objects.ImageMeta(
|
image=objects.ImageMeta(
|
||||||
|
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
||||||
name=self.image_name,
|
name=self.image_name,
|
||||||
properties=objects.ImageMetaProps(
|
properties=objects.ImageMetaProps(
|
||||||
|
hw_machine_type='q35',
|
||||||
hw_firmware_type='uefi',
|
hw_firmware_type='uefi',
|
||||||
hw_mem_encryption=image_prop))
|
hw_mem_encryption=image_prop))
|
||||||
)
|
)
|
||||||
|
@ -1341,8 +1345,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||||
'hw_mem_encryption image property',
|
'hw_mem_encryption image property',
|
||||||
{'hw:mem_encryption': extra_spec},
|
{'hw:mem_encryption': extra_spec},
|
||||||
image=objects.ImageMeta(
|
image=objects.ImageMeta(
|
||||||
|
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
||||||
name=self.image_name,
|
name=self.image_name,
|
||||||
properties=objects.ImageMetaProps(
|
properties=objects.ImageMetaProps(
|
||||||
|
hw_machine_type='q35',
|
||||||
hw_firmware_type='uefi',
|
hw_firmware_type='uefi',
|
||||||
hw_mem_encryption=image_prop))
|
hw_mem_encryption=image_prop))
|
||||||
)
|
)
|
||||||
|
|
|
@ -3798,32 +3798,50 @@ class MemEncryptionFlavorImageConflictTestCase(test.NoDBTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
|
class MemEncryptionRequestedInvalidImagePropsTestCase(test.NoDBTestCase):
|
||||||
flavor_name = 'm1.faketiny'
|
flavor_name = 'm1.faketiny'
|
||||||
image_name = 'fakecirros'
|
image_name = 'fakecirros'
|
||||||
|
image_id = '7ec4448e-f3fd-44b1-b172-9a7980f0f29f'
|
||||||
|
|
||||||
def _test_encrypted_memory_support_no_uefi(self, extra_spec, image_prop,
|
def _test_encrypted_memory_support_raises(self, enc_extra_spec,
|
||||||
requesters):
|
enc_image_prop, image_props,
|
||||||
|
error_data):
|
||||||
extra_specs = {}
|
extra_specs = {}
|
||||||
if extra_spec:
|
if enc_extra_spec:
|
||||||
extra_specs['hw:mem_encryption'] = extra_spec
|
extra_specs['hw:mem_encryption'] = enc_extra_spec
|
||||||
flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
|
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(
|
image_meta = fake_image_obj(
|
||||||
{'name': self.image_name}, {'hw_firmware_type': 'bios'},
|
{'id': self.image_id, 'name': self.image_name},
|
||||||
{'hw_mem_encryption': True} if image_prop else {})
|
{}, image_props)
|
||||||
error = (
|
exc = self.assertRaises(self.expected_exception,
|
||||||
"Memory encryption requested by %(requesters)s but image "
|
hw.get_mem_encryption_constraint,
|
||||||
"%(image_name)s doesn't have 'hw_firmware_type' property "
|
flavor, image_meta)
|
||||||
"set to 'uefi'"
|
self.assertEqual(self.expected_error % error_data, str(exc))
|
||||||
)
|
|
||||||
exc = self.assertRaises(
|
|
||||||
exception.FlavorImageConflict,
|
class MemEncryptionRequestedWithoutUEFITestCase(
|
||||||
hw.get_mem_encryption_constraint,
|
MemEncryptionRequestedInvalidImagePropsTestCase):
|
||||||
flavor, image_meta
|
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,
|
error_data = {'requesters': requesters,
|
||||||
'image_name': self.image_name}
|
'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):
|
def test_flavor_requires_encrypted_memory_support_no_uefi(self):
|
||||||
for extra_spec in ('1', 'true', 'True'):
|
for extra_spec in ('1', 'true', 'True'):
|
||||||
|
@ -3847,28 +3865,73 @@ class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
|
||||||
% (self.flavor_name, self.image_name))
|
% (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):
|
class MemEncryptionRequiredTestCase(test.NoDBTestCase):
|
||||||
flavor_name = "m1.faketiny"
|
flavor_name = "m1.faketiny"
|
||||||
image_name = 'fakecirros'
|
image_name = 'fakecirros'
|
||||||
|
image_id = '8a71a380-be23-47eb-9e72-9998586a0268'
|
||||||
|
|
||||||
@mock.patch.object(hw, 'LOG')
|
@mock.patch.object(hw, 'LOG')
|
||||||
def _test_encrypted_memory_support_required(self, extra_specs,
|
def _test_encrypted_memory_support_required(self, extra_specs,
|
||||||
image_props,
|
image_props,
|
||||||
requesters, mock_log):
|
requesters, mock_log):
|
||||||
flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
|
image_props['hw_firmware_type'] = 'uefi'
|
||||||
image_meta = objects.ImageMeta(name=self.image_name,
|
|
||||||
properties=image_props)
|
|
||||||
|
|
||||||
self.assertTrue(hw.get_mem_encryption_constraint(flavor, image_meta))
|
def _test_get_mem_encryption_constraint():
|
||||||
mock_log.debug.assert_has_calls([
|
flavor = objects.Flavor(name=self.flavor_name,
|
||||||
mock.call("Memory encryption requested by %s", requesters)
|
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):
|
def test_require_encrypted_memory_support_extra_spec(self):
|
||||||
for extra_spec in ('1', 'true', 'True'):
|
for extra_spec in ('1', 'true', 'True'):
|
||||||
self._test_encrypted_memory_support_required(
|
self._test_encrypted_memory_support_required(
|
||||||
{'hw:mem_encryption': extra_spec},
|
{'hw:mem_encryption': extra_spec},
|
||||||
objects.ImageMetaProps(hw_firmware_type='uefi'),
|
{},
|
||||||
"hw:mem_encryption extra spec in %s flavor" % self.flavor_name
|
"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'):
|
for image_prop in ('1', 'true', 'True'):
|
||||||
self._test_encrypted_memory_support_required(
|
self._test_encrypted_memory_support_required(
|
||||||
{},
|
{},
|
||||||
objects.ImageMetaProps(
|
{'hw_mem_encryption': image_prop},
|
||||||
hw_mem_encryption=image_prop,
|
|
||||||
hw_firmware_type='uefi'),
|
|
||||||
"hw_mem_encryption property of image %s" % self.image_name
|
"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'):
|
for image_prop in ('1', 'true', 'True'):
|
||||||
self._test_encrypted_memory_support_required(
|
self._test_encrypted_memory_support_required(
|
||||||
{'hw:mem_encryption': extra_spec},
|
{'hw:mem_encryption': extra_spec},
|
||||||
objects.ImageMetaProps(
|
{'hw_mem_encryption': image_prop},
|
||||||
hw_mem_encryption=image_prop,
|
|
||||||
hw_firmware_type='uefi'),
|
|
||||||
"hw:mem_encryption extra spec in %s flavor and "
|
"hw:mem_encryption extra spec in %s flavor and "
|
||||||
"hw_mem_encryption property of image %s" %
|
"hw_mem_encryption property of image %s" %
|
||||||
(self.flavor_name, self.image_name)
|
(self.flavor_name, self.image_name)
|
||||||
|
|
|
@ -1153,9 +1153,17 @@ def get_mem_encryption_constraint(flavor, image_meta):
|
||||||
2) the flavor and/or image request memory encryption, but the
|
2) the flavor and/or image request memory encryption, but the
|
||||||
image is missing hw_firmware_type=uefi
|
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 instance_type: Flavor object
|
||||||
:param image: an ImageMeta object
|
:param image: an ImageMeta object
|
||||||
:raises: nova.exception.FlavorImageConflict
|
:raises: nova.exception.FlavorImageConflict
|
||||||
|
:raises: nova.exception.InvalidMachineType
|
||||||
:returns: boolean indicating whether encryption of guest memory
|
:returns: boolean indicating whether encryption of guest memory
|
||||||
was requested
|
was requested
|
||||||
"""
|
"""
|
||||||
|
@ -1188,6 +1196,7 @@ def get_mem_encryption_constraint(flavor, image_meta):
|
||||||
image_meta.name)
|
image_meta.name)
|
||||||
|
|
||||||
_check_mem_encryption_uses_uefi_image(requesters, image_meta)
|
_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))
|
LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
|
||||||
return True
|
return True
|
||||||
|
@ -1215,7 +1224,7 @@ def _check_for_mem_encryption_requirement_conflicts(
|
||||||
|
|
||||||
|
|
||||||
def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
|
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
|
return
|
||||||
|
|
||||||
emsg = _(
|
emsg = _(
|
||||||
|
@ -1227,6 +1236,38 @@ def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
|
||||||
raise exception.FlavorImageConflict(emsg % data)
|
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):
|
def _get_numa_pagesize_constraint(flavor, image_meta):
|
||||||
"""Return the requested memory page size
|
"""Return the requested memory page size
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue