Enable booting of libvirt guests with AMD SEV memory encryption
Track compute node inventory for the new MEM_ENCRYPTION_CONTEXT resource class (added in os-resource-classes 0.4.0) which represents the number of guests a compute node can host concurrently with memory encrypted at the hardware level. This serves as a "master switch" for enabling SEV functionality, since all the code which takes advantage of the presence of this inventory in order to boot SEV-enabled guests is already in place, but none of it gets used until the inventory is non-zero. A discrete inventory is required because on AMD SEV-capable hardware, the memory controller has a fixed number of slots for holding encryption keys, one per guest. Typical early hardware only has 15 slots, thereby limiting the number of SEV guests which can be run concurrently to 15. nova needs to track how many slots are available and used in order to avoid attempting to exceed that limit in the hardware. Work is in progress to allow QEMU and libvirt to expose the number of slots available on SEV hardware; however until this is finished and released, it will not be possible for nova to programatically detect the correct value with which to populate the MEM_ENCRYPTION_CONTEXT inventory. So as a stop-gap, populate the inventory using the value manually provided by the cloud operator in a new configuration option CONF.libvirt.num_memory_encrypted_guests. Since this commit effectively enables SEV, also add all the relevant documentation as planned in the AMD SEV spec[0]: - Add operation.boot-encrypted-vm to the KVM hypervisor feature matrix. - Update the KVM section of the Configuration Guide. - Update the flavors section of the User Guide. - Add a release note. [0] http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html#documentation-impact blueprint: amd-sev-libvirt-support Change-Id: I659cb77f12a38a4d2fb118530ebb9de88d2ed30d
This commit is contained in:
parent
84db8b3f3d
commit
8e5d6767bb
@ -469,6 +469,262 @@ See `the KVM documentation
|
|||||||
<https://www.linux-kvm.org/page/Nested_Guests#Limitations>`_ for more
|
<https://www.linux-kvm.org/page/Nested_Guests#Limitations>`_ for more
|
||||||
information on these limitations.
|
information on these limitations.
|
||||||
|
|
||||||
|
.. _amd-sev:
|
||||||
|
|
||||||
|
AMD SEV (Secure Encrypted Virtualization)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
`Secure Encrypted Virtualization (SEV)`__ is a technology from AMD which
|
||||||
|
enables the memory for a VM to be encrypted with a key unique to the VM.
|
||||||
|
SEV is particularly applicable to cloud computing since it can reduce the
|
||||||
|
amount of trust VMs need to place in the hypervisor and administrator of
|
||||||
|
their host system.
|
||||||
|
|
||||||
|
__ https://developer.amd.com/sev/
|
||||||
|
|
||||||
|
Nova supports SEV from the Train release onwards.
|
||||||
|
|
||||||
|
Requirements for SEV
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
First the operator will need to ensure the following prerequisites are met:
|
||||||
|
|
||||||
|
- SEV-capable AMD hardware as Nova compute hosts
|
||||||
|
|
||||||
|
- An appropriately configured software stack on those compute hosts,
|
||||||
|
so that the various layers are all SEV ready:
|
||||||
|
|
||||||
|
- kernel >= 4.16
|
||||||
|
- QEMU >= 2.12
|
||||||
|
- libvirt >= 4.5
|
||||||
|
- ovmf >= commit 75b7aa9528bd 2018-07-06
|
||||||
|
|
||||||
|
.. _deploying-sev-capable-infrastructure:
|
||||||
|
|
||||||
|
Deploying SEV-capable infrastructure
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
In order for users to be able to use SEV, the operator will need to
|
||||||
|
perform the following steps:
|
||||||
|
|
||||||
|
- Configure the :oslo.config:option:`libvirt.num_memory_encrypted_guests`
|
||||||
|
option in :file:`nova.conf` to represent the number of guests an SEV
|
||||||
|
compute node can host concurrently with memory encrypted at the
|
||||||
|
hardware level. For example:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[libvirt]
|
||||||
|
num_memory_encrypted_guests = 15
|
||||||
|
|
||||||
|
Initially this is required because on AMD SEV-capable hardware, the
|
||||||
|
memory controller has a fixed number of slots for holding encryption
|
||||||
|
keys, one per guest. For example, at the time of writing, earlier
|
||||||
|
generations of hardware only have 15 slots, thereby limiting the
|
||||||
|
number of SEV guests which can be run concurrently to 15. Nova
|
||||||
|
needs to track how many slots are available and used in order to
|
||||||
|
avoid attempting to exceed that limit in the hardware.
|
||||||
|
|
||||||
|
At the time of writing (September 2019), work is in progress to allow
|
||||||
|
QEMU and libvirt to expose the number of slots available on SEV
|
||||||
|
hardware; however until this is finished and released, it will not be
|
||||||
|
possible for Nova to programatically detect the correct value. So this
|
||||||
|
configuration option serves as a stop-gap, allowing the cloud operator
|
||||||
|
to provide this value manually.
|
||||||
|
|
||||||
|
- Ensure that sufficient memory is reserved on the SEV compute hosts
|
||||||
|
for host-level services to function correctly at all times. This is
|
||||||
|
particularly important when hosting SEV-enabled guests, since they
|
||||||
|
pin pages in RAM, preventing any memory overcommit which may be in
|
||||||
|
normal operation on other compute hosts.
|
||||||
|
|
||||||
|
It is `recommended`__ to achieve this by configuring an ``rlimit`` at
|
||||||
|
the ``/machine.slice`` top-level ``cgroup`` on the host, with all VMs
|
||||||
|
placed inside that. (For extreme detail, see `this discussion on the
|
||||||
|
spec`__.)
|
||||||
|
|
||||||
|
__ http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html#memory-reservation-solutions
|
||||||
|
__ https://review.opendev.org/#/c/641994/2/specs/train/approved/amd-sev-libvirt-support.rst@167
|
||||||
|
|
||||||
|
An alternative approach is to configure the
|
||||||
|
:oslo.config:option:`reserved_host_memory_mb` option in the
|
||||||
|
``[compute]`` section of :file:`nova.conf`, based on the expected
|
||||||
|
maximum number of SEV guests simultaneously running on the host, and
|
||||||
|
the details provided in `an earlier version of the AMD SEV spec`__
|
||||||
|
regarding memory region sizes, which cover how to calculate it
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
__ https://specs.openstack.org/openstack/nova-specs/specs/stein/approved/amd-sev-libvirt-support.html#proposed-change
|
||||||
|
|
||||||
|
See `the Memory Locking and Accounting section of the AMD SEV spec`__
|
||||||
|
and `previous discussion for further details`__.
|
||||||
|
|
||||||
|
__ http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html#memory-locking-and-accounting
|
||||||
|
__ https://review.opendev.org/#/c/641994/2/specs/train/approved/amd-sev-libvirt-support.rst@167
|
||||||
|
|
||||||
|
- A cloud administrator will need to define one or more SEV-enabled
|
||||||
|
flavors :ref:`as described in the user guide
|
||||||
|
<extra-specs-memory-encryption>`, unless it is sufficient for users
|
||||||
|
to define SEV-enabled images.
|
||||||
|
|
||||||
|
Additionally the cloud operator should consider the following optional
|
||||||
|
step:
|
||||||
|
|
||||||
|
- Configure :oslo.config:option:`libvirt.hw_machine_type` on all
|
||||||
|
SEV-capable compute hosts to include ``x86_64=q35``, so that all
|
||||||
|
x86_64 images use the ``q35`` machine type by default. (Currently
|
||||||
|
Nova defaults to the ``pc`` machine type for the ``x86_64``
|
||||||
|
architecture, although `it is expected that this will change in the
|
||||||
|
future`__.)
|
||||||
|
|
||||||
|
Changing the default from ``pc`` to ``q35`` makes the creation and
|
||||||
|
configuration of images by users more convenient by removing the
|
||||||
|
need for the ``hw_machine_type`` property to be set to ``q35`` on
|
||||||
|
every image for which SEV booting is desired.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
Consider carefully whether to set this option. It is
|
||||||
|
particularly important since a limitation of the implementation
|
||||||
|
prevents the user from receiving an error message with a helpful
|
||||||
|
explanation if they try to boot an SEV guest when neither this
|
||||||
|
configuration option nor the image property are set to select
|
||||||
|
a ``q35`` machine type.
|
||||||
|
|
||||||
|
On the other hand, setting it to ``q35`` may have other
|
||||||
|
undesirable side-effects on other images which were expecting to
|
||||||
|
be booted with ``pc``, so it is suggested to set it on a single
|
||||||
|
compute node or aggregate, and perform careful testing of typical
|
||||||
|
images before rolling out the setting to all SEV-capable compute
|
||||||
|
hosts.
|
||||||
|
|
||||||
|
__ https://bugs.launchpad.net/nova/+bug/1780138
|
||||||
|
|
||||||
|
Launching SEV instances
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Once an operator has covered the above steps, users can launch SEV
|
||||||
|
instances either by requesting a flavor for which the operator set the
|
||||||
|
``hw:mem_encryption`` extra spec to ``True``, or by using an image
|
||||||
|
with the ``hw_mem_encryption`` property set to ``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 adding ``trait:HW_CPU_X86_AMD_SEV=required`` to the
|
||||||
|
flavor extra specs or image properties.
|
||||||
|
|
||||||
|
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
|
||||||
|
machine type is set to ``q35``. This can be set per image by setting
|
||||||
|
the image property ``hw_machine_type=q35``, or per compute node by
|
||||||
|
the operator via :oslo.config:option:`libvirt.hw_machine_type` as
|
||||||
|
explained above.
|
||||||
|
|
||||||
|
Impermanent limitations
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The following limitations may be removed in the future as the
|
||||||
|
hardware, firmware, and various layers of software receive new
|
||||||
|
features:
|
||||||
|
|
||||||
|
- SEV-encrypted VMs cannot yet be live-migrated or suspended,
|
||||||
|
therefore they will need to be fully shut down before migrating off
|
||||||
|
an SEV host, e.g. if maintenance is required on the host.
|
||||||
|
|
||||||
|
- SEV-encrypted VMs cannot contain directly accessible host devices
|
||||||
|
(PCI passthrough). So for example mdev vGPU support will not
|
||||||
|
currently work. However technologies based on vhost-user should
|
||||||
|
work fine.
|
||||||
|
|
||||||
|
- The boot disk of SEV-encrypted VMs cannot be ``virtio-blk``. Using
|
||||||
|
``virtio-scsi`` or SATA for the boot disk works as expected, as does
|
||||||
|
``virtio-blk`` for non-boot disks.
|
||||||
|
|
||||||
|
- Operators will initially be required to manually specify the upper
|
||||||
|
limit of SEV guests for each compute host, via the new
|
||||||
|
:oslo.config:option:`libvirt.num_memory_encrypted_guests` configuration
|
||||||
|
option described above. This is a short-term workaround to the current
|
||||||
|
lack of mechanism for programmatically discovering the SEV guest limit
|
||||||
|
via libvirt.
|
||||||
|
|
||||||
|
This config option may later be demoted to a fallback value for
|
||||||
|
cases where the limit cannot be detected programmatically, or even
|
||||||
|
removed altogether when Nova's minimum QEMU version guarantees that
|
||||||
|
it can always be detected.
|
||||||
|
|
||||||
|
Permanent limitations
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The following limitations are expected long-term:
|
||||||
|
|
||||||
|
- The number of SEV guests allowed to run concurrently will always be
|
||||||
|
limited. `On the first generation of EPYC machines it will be
|
||||||
|
limited to 15 guests`__; however this limit becomes much higher with
|
||||||
|
the second generation (Rome).
|
||||||
|
|
||||||
|
__ https://www.redhat.com/archives/libvir-list/2019-January/msg00652.html
|
||||||
|
|
||||||
|
- The operating system running in an encrypted virtual machine must
|
||||||
|
contain SEV support.
|
||||||
|
|
||||||
|
- The ``q35`` machine type does not provide an IDE controller,
|
||||||
|
therefore IDE devices are not supported. In particular this means
|
||||||
|
that Nova's libvirt driver's current default behaviour on the x86_64
|
||||||
|
architecture of attaching the config drive as an ``iso9660`` IDE
|
||||||
|
CD-ROM device will not work. There are two potential workarounds:
|
||||||
|
|
||||||
|
#. Change :oslo.config:option:`config_drive_format` in
|
||||||
|
:file:`nova.conf` from its default value of ``iso9660`` to ``vfat``.
|
||||||
|
This will result in ``virtio`` being used instead. However this
|
||||||
|
per-host setting could potentially break images with legacy OSes
|
||||||
|
which expect the config drive to be an IDE CD-ROM. It would also
|
||||||
|
not deal with other CD-ROM devices.
|
||||||
|
|
||||||
|
#. Set the (largely `undocumented
|
||||||
|
<https://bugs.launchpad.net/glance/+bug/1808868>`_)
|
||||||
|
``hw_cdrom_bus`` image property to ``virtio``, which is
|
||||||
|
recommended as a replacement for ``ide``, and ``hw_scsi_model``
|
||||||
|
to ``virtio-scsi``.
|
||||||
|
|
||||||
|
Some potentially cleaner long-term solutions which require code
|
||||||
|
changes have been suggested in the `Work Items section of the SEV
|
||||||
|
spec`__.
|
||||||
|
|
||||||
|
__ http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html#work-items
|
||||||
|
|
||||||
|
Non-limitations
|
||||||
|
---------------
|
||||||
|
|
||||||
|
For the sake of eliminating any doubt, the following actions are *not*
|
||||||
|
expected to be limited when SEV encryption is used:
|
||||||
|
|
||||||
|
- Cold migration or shelve, since they power off the VM before the
|
||||||
|
operation at which point there is no encrypted memory (although this
|
||||||
|
could change since there is work underway to add support for `PMEM
|
||||||
|
<https://pmem.io/>`_)
|
||||||
|
|
||||||
|
- Snapshot, since it only snapshots the disk
|
||||||
|
|
||||||
|
- ``nova evacuate`` (despite the name, more akin to resurrection than
|
||||||
|
evacuation), since this is only initiated when the VM is no longer
|
||||||
|
running
|
||||||
|
|
||||||
|
- Attaching any volumes, as long as they do not require attaching via
|
||||||
|
an IDE bus
|
||||||
|
|
||||||
|
- Use of spice / VNC / serial / RDP consoles
|
||||||
|
|
||||||
|
- `VM guest virtual NUMA (a.k.a. vNUMA)
|
||||||
|
<https://www.suse.com/documentation/sles-12/singlehtml/article_vt_best_practices/article_vt_best_practices.html#sec.vt.best.perf.numa.vmguest>`_
|
||||||
|
|
||||||
|
For further technical details, see `the nova spec for SEV support`__.
|
||||||
|
|
||||||
|
__ http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html
|
||||||
|
|
||||||
|
|
||||||
Guest agent support
|
Guest agent support
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -538,6 +538,18 @@ NUMA topology
|
|||||||
greater than the available number of CPUs or memory respectively, an
|
greater than the available number of CPUs or memory respectively, an
|
||||||
exception is raised.
|
exception is raised.
|
||||||
|
|
||||||
|
.. _extra-specs-memory-encryption:
|
||||||
|
|
||||||
|
Hardware encryption of guest memory
|
||||||
|
If there are compute hosts which support encryption of guest memory
|
||||||
|
at the hardware level, this functionality can be requested via the
|
||||||
|
``hw:mem_encryption`` extra spec parameter:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack flavor set FLAVOR-NAME \
|
||||||
|
--property hw:mem_encryption=True
|
||||||
|
|
||||||
.. _extra-specs-realtime-policy:
|
.. _extra-specs-realtime-policy:
|
||||||
|
|
||||||
CPU real-time policy
|
CPU real-time policy
|
||||||
|
@ -1664,3 +1664,31 @@ driver.libvirt-vz-vm=missing
|
|||||||
driver.libvirt-vz-ct=missing
|
driver.libvirt-vz-ct=missing
|
||||||
driver.powervm=missing
|
driver.powervm=missing
|
||||||
driver.zvm=missing
|
driver.zvm=missing
|
||||||
|
|
||||||
|
[operation.boot-encrypted-vm]
|
||||||
|
title=Boot instance with secure encrypted memory
|
||||||
|
status=optional
|
||||||
|
notes=The feature allows VMs to be booted with their memory
|
||||||
|
hardware-encrypted with a key specific to the VM, to help
|
||||||
|
protect the data residing in the VM against access from anyone
|
||||||
|
other than the user of the VM. The Configuration and Security
|
||||||
|
Guides specify usage of this feature.
|
||||||
|
cli=openstack server create <usual server create parameters>
|
||||||
|
driver.xenserver=missing
|
||||||
|
driver.libvirt-kvm-x86=partial
|
||||||
|
driver-notes.libvirt-kvm-x86=This feature is currently only
|
||||||
|
available with hosts which support the SEV (Secure Encrypted
|
||||||
|
Virtualization) technology from AMD.
|
||||||
|
driver.libvirt-kvm-aarch64=missing
|
||||||
|
driver.libvirt-kvm-ppc64=missing
|
||||||
|
driver.libvirt-kvm-s390x=missing
|
||||||
|
driver.libvirt-qemu-x86=missing
|
||||||
|
driver.libvirt-lxc=missing
|
||||||
|
driver.libvirt-xen=missing
|
||||||
|
driver.vmware=missing
|
||||||
|
driver.hyperv=missing
|
||||||
|
driver.ironic=missing
|
||||||
|
driver.libvirt-vz-vm=missing
|
||||||
|
driver.libvirt-vz-ct=missing
|
||||||
|
driver.powervm=missing
|
||||||
|
driver.zvm=missing
|
||||||
|
@ -718,10 +718,11 @@ http://man7.org/linux/man-pages/man7/random.7.html.
|
|||||||
help='For qemu or KVM guests, set this option to specify '
|
help='For qemu or KVM guests, set this option to specify '
|
||||||
'a default machine type per host architecture. '
|
'a default machine type per host architecture. '
|
||||||
'You can find a list of supported machine types '
|
'You can find a list of supported machine types '
|
||||||
'in your environment by checking the output of '
|
'in your environment by checking the output of the '
|
||||||
'the "virsh capabilities" command. The format of the '
|
':command:`virsh capabilities` command. The format of '
|
||||||
'value for this config option is host-arch=machine-type. '
|
'the value for this config option is '
|
||||||
'For example: x86_64=machinetype1,armv7l=machinetype2'),
|
'``host-arch=machine-type``. For example: '
|
||||||
|
'``x86_64=machinetype1,armv7l=machinetype2``.'),
|
||||||
cfg.StrOpt('sysinfo_serial',
|
cfg.StrOpt('sysinfo_serial',
|
||||||
default='unique',
|
default='unique',
|
||||||
choices=(
|
choices=(
|
||||||
@ -840,6 +841,57 @@ Related options:
|
|||||||
|
|
||||||
* ``virt_type`` must be set to ``kvm`` or ``qemu``.
|
* ``virt_type`` must be set to ``kvm`` or ``qemu``.
|
||||||
* ``ram_allocation_ratio`` must be set to 1.0.
|
* ``ram_allocation_ratio`` must be set to 1.0.
|
||||||
|
"""),
|
||||||
|
cfg.IntOpt('num_memory_encrypted_guests',
|
||||||
|
default=None,
|
||||||
|
min=0,
|
||||||
|
help="""
|
||||||
|
Maximum number of guests with encrypted memory which can run
|
||||||
|
concurrently on this compute host.
|
||||||
|
|
||||||
|
For now this is only relevant for AMD machines which support SEV
|
||||||
|
(Secure Encrypted Virtualisation). Such machines have a limited
|
||||||
|
number of slots in their memory controller for storing encryption
|
||||||
|
keys. Each running guest with encrypted memory will consume one of
|
||||||
|
these slots.
|
||||||
|
|
||||||
|
The option may be reused for other equivalent technologies in the
|
||||||
|
future. If the machine does not support memory encryption, the option
|
||||||
|
will be ignored and inventory will be set to 0.
|
||||||
|
|
||||||
|
If the machine does support memory encryption, *for now* a value of
|
||||||
|
``None`` means an effectively unlimited inventory, i.e. no limit will
|
||||||
|
be imposed by Nova on the number of SEV guests which can be launched,
|
||||||
|
even though the underlying hardware will enforce its own limit.
|
||||||
|
However it is expected that in the future, auto-detection of the
|
||||||
|
inventory from the hardware will become possible, at which point
|
||||||
|
``None`` will cause auto-detection to automatically impose the correct
|
||||||
|
limit.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When deciding whether to use the default of ``None`` or manually
|
||||||
|
impose a limit, operators should carefully weigh the benefits
|
||||||
|
vs. the risk. The benefits are a) immediate convenience since
|
||||||
|
nothing needs to be done now, and b) convenience later when
|
||||||
|
upgrading compute hosts to future versions of Nova, since again
|
||||||
|
nothing will need to be done for the correct limit to be
|
||||||
|
automatically imposed. However the risk is that until
|
||||||
|
auto-detection is implemented, users may be able to attempt to
|
||||||
|
launch guests with encrypted memory on hosts which have already
|
||||||
|
reached the maximum number of guests simultaneously running with
|
||||||
|
encrypted memory. This risk may be mitigated by other limitations
|
||||||
|
which operators can impose, for example if the smallest RAM
|
||||||
|
footprint of any flavor imposes a maximum number of simultaneously
|
||||||
|
running guests which is less than or equal to the SEV limit.
|
||||||
|
|
||||||
|
Related options:
|
||||||
|
|
||||||
|
* :oslo.config:option:`libvirt.virt_type` must be set to ``kvm``.
|
||||||
|
|
||||||
|
* It's recommended to consider including ``x86_64=q35`` in
|
||||||
|
:oslo.config:option:`libvirt.hw_machine_type`; see
|
||||||
|
:ref:`deploying-sev-capable-infrastructure` for more on this.
|
||||||
"""),
|
"""),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -14,10 +14,12 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
import os_resource_classes as orc
|
||||||
import os_traits as ost
|
import os_traits as ost
|
||||||
|
|
||||||
|
|
||||||
from nova import conf
|
from nova import conf
|
||||||
|
from nova.db import constants as db_const
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.tests.functional.libvirt import integrated_helpers
|
from nova.tests.functional.libvirt import integrated_helpers
|
||||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||||
@ -30,6 +32,23 @@ class LibvirtReportTraitsTestBase(
|
|||||||
integrated_helpers.LibvirtProviderUsageBaseTestCase):
|
integrated_helpers.LibvirtProviderUsageBaseTestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def assertMemEncryptionSlotsEqual(self, slots):
|
||||||
|
inventory = self._get_provider_inventory(self.host_uuid)
|
||||||
|
if slots == 0:
|
||||||
|
self.assertNotIn(orc.MEM_ENCRYPTION_CONTEXT, inventory)
|
||||||
|
else:
|
||||||
|
self.assertEqual(
|
||||||
|
inventory[orc.MEM_ENCRYPTION_CONTEXT],
|
||||||
|
{
|
||||||
|
'total': slots,
|
||||||
|
'min_unit': 1,
|
||||||
|
'max_unit': 1,
|
||||||
|
'step_size': 1,
|
||||||
|
'allocation_ratio': 1.0,
|
||||||
|
'reserved': 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LibvirtReportTraitsTests(LibvirtReportTraitsTestBase):
|
class LibvirtReportTraitsTests(LibvirtReportTraitsTestBase):
|
||||||
def test_report_cpu_traits(self):
|
def test_report_cpu_traits(self):
|
||||||
@ -77,6 +96,10 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
Then test that if the SEV capability appears (again via
|
Then test that if the SEV capability appears (again via
|
||||||
mocking), after a restart of the compute service, the trait
|
mocking), after a restart of the compute service, the trait
|
||||||
gets registered on the compute host.
|
gets registered on the compute host.
|
||||||
|
|
||||||
|
Also test that on both occasions, the inventory of the
|
||||||
|
MEM_ENCRYPTION_CONTEXT resource class on the compute host
|
||||||
|
corresponds to the absence or presence of the SEV capability.
|
||||||
"""
|
"""
|
||||||
self.assertFalse(self.compute.driver._host.supports_amd_sev)
|
self.assertFalse(self.compute.driver._host.supports_amd_sev)
|
||||||
|
|
||||||
@ -88,6 +111,8 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
traits = self._get_provider_traits(self.host_uuid)
|
traits = self._get_provider_traits(self.host_uuid)
|
||||||
self.assertNotIn(sev_trait, traits)
|
self.assertNotIn(sev_trait, traits)
|
||||||
|
|
||||||
|
self.assertMemEncryptionSlotsEqual(0)
|
||||||
|
|
||||||
# Now simulate the host gaining SEV functionality. Here we
|
# Now simulate the host gaining SEV functionality. Here we
|
||||||
# simulate a kernel update or reconfiguration which causes the
|
# simulate a kernel update or reconfiguration which causes the
|
||||||
# kvm-amd kernel module's "sev" parameter to become available
|
# kvm-amd kernel module's "sev" parameter to become available
|
||||||
@ -121,6 +146,8 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
# Sanity check that we've still got the trait globally.
|
# Sanity check that we've still got the trait globally.
|
||||||
self.assertIn(sev_trait, self._get_all_traits())
|
self.assertIn(sev_trait, self._get_all_traits())
|
||||||
|
|
||||||
|
self.assertMemEncryptionSlotsEqual(db_const.MAX_INT)
|
||||||
|
|
||||||
|
|
||||||
class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
||||||
STUB_INIT_HOST = False
|
STUB_INIT_HOST = False
|
||||||
@ -132,6 +159,7 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
new=fakelibvirt.virConnect._domain_capability_features_with_SEV)
|
new=fakelibvirt.virConnect._domain_capability_features_with_SEV)
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(LibvirtReportSevTraitsTests, self).setUp()
|
super(LibvirtReportSevTraitsTests, self).setUp()
|
||||||
|
self.flags(num_memory_encrypted_guests=16, group='libvirt')
|
||||||
self.start_compute()
|
self.start_compute()
|
||||||
|
|
||||||
def test_sev_trait_on_off(self):
|
def test_sev_trait_on_off(self):
|
||||||
@ -143,6 +171,10 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
Then test that if the SEV capability disappears (again via
|
Then test that if the SEV capability disappears (again via
|
||||||
mocking), after a restart of the compute service, the trait
|
mocking), after a restart of the compute service, the trait
|
||||||
gets removed from the compute host.
|
gets removed from the compute host.
|
||||||
|
|
||||||
|
Also test that on both occasions, the inventory of the
|
||||||
|
MEM_ENCRYPTION_CONTEXT resource class on the compute host
|
||||||
|
corresponds to the absence or presence of the SEV capability.
|
||||||
"""
|
"""
|
||||||
self.assertTrue(self.compute.driver._host.supports_amd_sev)
|
self.assertTrue(self.compute.driver._host.supports_amd_sev)
|
||||||
|
|
||||||
@ -154,6 +186,8 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
traits = self._get_provider_traits(self.host_uuid)
|
traits = self._get_provider_traits(self.host_uuid)
|
||||||
self.assertIn(sev_trait, traits)
|
self.assertIn(sev_trait, traits)
|
||||||
|
|
||||||
|
self.assertMemEncryptionSlotsEqual(16)
|
||||||
|
|
||||||
# Now simulate the host losing SEV functionality. Here we
|
# Now simulate the host losing SEV functionality. Here we
|
||||||
# simulate a kernel downgrade or reconfiguration which causes
|
# simulate a kernel downgrade or reconfiguration which causes
|
||||||
# the kvm-amd kernel module's "sev" parameter to become
|
# the kvm-amd kernel module's "sev" parameter to become
|
||||||
@ -177,3 +211,5 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
|||||||
|
|
||||||
# Sanity check that we've still got the trait globally.
|
# Sanity check that we've still got the trait globally.
|
||||||
self.assertIn(sev_trait, self._get_all_traits())
|
self.assertIn(sev_trait, self._get_all_traits())
|
||||||
|
|
||||||
|
self.assertMemEncryptionSlotsEqual(0)
|
||||||
|
@ -70,6 +70,7 @@ from nova.compute import vm_states
|
|||||||
import nova.conf
|
import nova.conf
|
||||||
from nova import context
|
from nova import context
|
||||||
from nova.db import api as db
|
from nova.db import api as db
|
||||||
|
from nova.db import constants as db_const
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.network import model as network_model
|
from nova.network import model as network_model
|
||||||
from nova import objects
|
from nova import objects
|
||||||
@ -23679,3 +23680,69 @@ class TestLibvirtMultiattach(test.NoDBTestCase):
|
|||||||
# calls = [mock.call(lv_ver=libvirt_driver.MIN_LIBVIRT_MULTIATTACH),
|
# calls = [mock.call(lv_ver=libvirt_driver.MIN_LIBVIRT_MULTIATTACH),
|
||||||
# mock.call(hv_ver=(2, 10, 0))]
|
# mock.call(hv_ver=(2, 10, 0))]
|
||||||
# has_min_version.assert_has_calls(calls)
|
# has_min_version.assert_has_calls(calls)
|
||||||
|
|
||||||
|
|
||||||
|
vc = fakelibvirt.virConnect
|
||||||
|
|
||||||
|
|
||||||
|
class TestLibvirtSEV(test.NoDBTestCase):
|
||||||
|
"""Libvirt driver tests for AMD SEV support."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestLibvirtSEV, self).setUp()
|
||||||
|
self.useFixture(fakelibvirt.FakeLibvirtFixture())
|
||||||
|
self.driver = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'exists', new=mock.Mock(return_value=False))
|
||||||
|
class TestLibvirtSEVUnsupported(TestLibvirtSEV):
|
||||||
|
def test_get_mem_encrypted_slots_no_config(self):
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||||
|
|
||||||
|
def test_get_mem_encrypted_slots_config_zero(self):
|
||||||
|
self.flags(num_memory_encrypted_guests=0, group='libvirt')
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||||
|
|
||||||
|
@mock.patch.object(libvirt_driver.LOG, 'warning')
|
||||||
|
def test_get_mem_encrypted_slots_config_non_zero_unsupported(
|
||||||
|
self, mock_log):
|
||||||
|
self.flags(num_memory_encrypted_guests=16, group='libvirt')
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
# Still zero without mocked SEV support
|
||||||
|
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||||
|
mock_log.assert_called_with(
|
||||||
|
'Host is configured with libvirt.num_memory_encrypted_guests '
|
||||||
|
'set to %d, but is not SEV-capable.', 16)
|
||||||
|
|
||||||
|
def test_get_mem_encrypted_slots_unsupported(self):
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(vc, '_domain_capability_features',
|
||||||
|
new=vc._domain_capability_features_with_SEV)
|
||||||
|
class TestLibvirtSEVSupported(TestLibvirtSEV):
|
||||||
|
"""Libvirt driver tests for when AMD SEV support is present."""
|
||||||
|
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
|
||||||
|
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
|
||||||
|
def test_get_mem_encrypted_slots_unlimited(self):
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
self.assertEqual(db_const.MAX_INT,
|
||||||
|
self.driver._get_memory_encrypted_slots())
|
||||||
|
|
||||||
|
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
|
||||||
|
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
|
||||||
|
def test_get_mem_encrypted_slots_config_non_zero_supported(self):
|
||||||
|
self.flags(num_memory_encrypted_guests=16, group='libvirt')
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
self.assertEqual(16, self.driver._get_memory_encrypted_slots())
|
||||||
|
|
||||||
|
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
|
||||||
|
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
|
||||||
|
@mock.patch.object(libvirt_driver.LOG, 'warning')
|
||||||
|
def test_get_mem_encrypted_slots_config_zero_supported(self, mock_log):
|
||||||
|
self.flags(num_memory_encrypted_guests=0, group='libvirt')
|
||||||
|
self.driver._host._set_amd_sev_support()
|
||||||
|
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||||
|
@ -84,6 +84,7 @@ from nova.console import serial as serial_console
|
|||||||
from nova.console import type as ctype
|
from nova.console import type as ctype
|
||||||
from nova import context as nova_context
|
from nova import context as nova_context
|
||||||
from nova import crypto
|
from nova import crypto
|
||||||
|
from nova.db import constants as db_const
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova import image
|
from nova import image
|
||||||
@ -6875,6 +6876,17 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memory_enc_slots = self._get_memory_encrypted_slots()
|
||||||
|
if memory_enc_slots > 0:
|
||||||
|
result[orc.MEM_ENCRYPTION_CONTEXT] = {
|
||||||
|
'total': memory_enc_slots,
|
||||||
|
'min_unit': 1,
|
||||||
|
'max_unit': 1,
|
||||||
|
'step_size': 1,
|
||||||
|
'allocation_ratio': 1.0,
|
||||||
|
'reserved': 0,
|
||||||
|
}
|
||||||
|
|
||||||
# If a sharing DISK_GB provider exists in the provider tree, then our
|
# If a sharing DISK_GB provider exists in the provider tree, then our
|
||||||
# storage is shared, and we should not report the DISK_GB inventory in
|
# storage is shared, and we should not report the DISK_GB inventory in
|
||||||
# the compute node provider.
|
# the compute node provider.
|
||||||
@ -6915,6 +6927,35 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
# so that spawn() or other methods can access it thru a getter
|
# so that spawn() or other methods can access it thru a getter
|
||||||
self.provider_tree = copy.deepcopy(provider_tree)
|
self.provider_tree = copy.deepcopy(provider_tree)
|
||||||
|
|
||||||
|
def _get_memory_encrypted_slots(self):
|
||||||
|
slots = CONF.libvirt.num_memory_encrypted_guests
|
||||||
|
if not self._host.supports_amd_sev:
|
||||||
|
if slots and slots > 0:
|
||||||
|
LOG.warning("Host is configured with "
|
||||||
|
"libvirt.num_memory_encrypted_guests set to "
|
||||||
|
"%d, but is not SEV-capable.", slots)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# NOTE(aspiers): Auto-detection of the number of available
|
||||||
|
# slots for AMD SEV is not yet possible, so honor the
|
||||||
|
# configured value, or impose no limit if this is not
|
||||||
|
# specified. This does incur a risk that if operators don't
|
||||||
|
# read the instructions and configure the maximum correctly,
|
||||||
|
# the maximum could be exceeded resulting in SEV guests
|
||||||
|
# failing at launch-time. However at least SEV guests will
|
||||||
|
# launch until the maximum, and when auto-detection code is
|
||||||
|
# added later, an upgrade will magically fix the issue.
|
||||||
|
#
|
||||||
|
# Note also that the configured value can be 0 on an
|
||||||
|
# SEV-capable host, since there might conceivably be good
|
||||||
|
# reasons for the operator to want to disable SEV even when
|
||||||
|
# it's available (e.g. due to performance impact, or
|
||||||
|
# implementation bugs which may surface later).
|
||||||
|
if slots is not None:
|
||||||
|
return slots
|
||||||
|
else:
|
||||||
|
return db_const.MAX_INT
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_reshape_needed_vgpu_on_root(provider_tree, nodename):
|
def _is_reshape_needed_vgpu_on_root(provider_tree, nodename):
|
||||||
"""Determine if root RP has VGPU inventories.
|
"""Determine if root RP has VGPU inventories.
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The libvirt driver can now support requests for guest RAM to be
|
||||||
|
encrypted at the hardware level, if there are compute hosts which
|
||||||
|
support it. Currently only AMD SEV (Secure Encrypted
|
||||||
|
Virtualization) is supported, and it has certain minimum version
|
||||||
|
requirements regarding the kernel, QEMU, and libvirt.
|
||||||
|
|
||||||
|
Memory encryption can be required either via flavor which has the
|
||||||
|
``hw:mem_encryption`` extra spec set to ``True``, or via an image
|
||||||
|
which has the ``hw_mem_encryption`` property set to ``True``.
|
||||||
|
These do not inherently cause a preference for SEV-capable
|
||||||
|
hardware, but for now SEV is the only way of fulfilling the
|
||||||
|
requirement. 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 adding
|
||||||
|
``trait:HW_CPU_X86_AMD_SEV=required`` to the flavor extra specs or
|
||||||
|
image properties.
|
||||||
|
|
||||||
|
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 machine type is set to ``q35``. This can be set per
|
||||||
|
image by setting the image property ``hw_machine_type=q35``, or
|
||||||
|
per compute node by the operator via the ``hw_machine_type``
|
||||||
|
configuration option in the ``[libvirt]`` section of
|
||||||
|
:file:`nova.conf`.
|
||||||
|
|
||||||
|
For information on how to set up support for AMD SEV, please see
|
||||||
|
the `KVM section of the Configuration Guide
|
||||||
|
<https://docs.openstack.org/nova/latest/admin/configuration/hypervisor-kvm.html#amd-sev>`_.
|
Loading…
Reference in New Issue
Block a user