Skip to content

Commit 1738b52

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Detect maximum number of SEV guests automatically"
2 parents 670ba20 + 03055de commit 1738b52

File tree

9 files changed

+141
-64
lines changed

9 files changed

+141
-64
lines changed

doc/source/admin/sev.rst

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -91,32 +91,31 @@ steps:
9191
needs to track how many slots are available and used in order to
9292
avoid attempting to exceed that limit in the hardware.
9393

94-
At the time of writing (September 2019), work is in progress to
95-
allow QEMU and libvirt to expose the number of slots available on
96-
SEV hardware; however until this is finished and released, it will
97-
not be possible for Nova to programmatically detect the correct
98-
value.
99-
100-
So this configuration option serves as a stop-gap, allowing the
101-
cloud operator the option of providing this value manually. It may
102-
later be demoted to a fallback value for cases where the limit
103-
cannot be detected programmatically, or even removed altogether when
104-
Nova's minimum QEMU version guarantees that it can always be
105-
detected.
94+
Since version 8.0.0, libvirt exposes maximun mumber of SEV guests
95+
which can run concurrently in its host, so the limit is automatically
96+
detected using this feature.
97+
98+
However in case an older version of libvirt is used, it is not possible for
99+
Nova to programmatically detect the correct value and Nova imposes no limit.
100+
So this configuration option serves as a stop-gap, allowing the cloud
101+
operator the option of providing this value manually.
102+
103+
This option also allows the cloud operator to set the limit lower than
104+
the actual hard limit.
106105

107106
.. note::
108107

109-
When deciding whether to use the default of ``None`` or manually
110-
impose a limit, operators should carefully weigh the benefits
111-
vs. the risk. The benefits of using the default are a) immediate
112-
convenience since nothing needs to be done now, and b) convenience
113-
later when upgrading compute hosts to future versions of Nova,
114-
since again nothing will need to be done for the correct limit to
115-
be automatically imposed. However the risk is that until
116-
auto-detection is implemented, users may be able to attempt to
117-
launch guests with encrypted memory on hosts which have already
118-
reached the maximum number of guests simultaneously running with
119-
encrypted memory. This risk may be mitigated by other limitations
108+
If libvirt older than 8.0.0 is used, operators should carefully weigh
109+
the benefits vs. the risk when deciding whether to use the default of
110+
``None`` or manually impose a limit.
111+
The benefits of using the default are a) immediate convenience since
112+
nothing needs to be done now, and b) convenience later when upgrading
113+
compute hosts to future versions of libvirt, since again nothing will
114+
need to be done for the correct limit to be automatically imposed.
115+
However the risk is that until auto-detection is implemented, users may
116+
be able to attempt to launch guests with encrypted memory on hosts which
117+
have already reached the maximum number of guests simultaneously running
118+
with encrypted memory. This risk may be mitigated by other limitations
120119
which operators can impose, for example if the smallest RAM
121120
footprint of any flavor imposes a maximum number of simultaneously
122121
running guests which is less than or equal to the SEV limit.
@@ -221,16 +220,6 @@ features:
221220
include using ``hw_disk_bus=scsi`` with
222221
``hw_scsi_model=virtio-scsi`` , or ``hw_disk_bus=sata``.
223222

224-
- QEMU and libvirt cannot yet expose the number of slots available for
225-
encrypted guests in the memory controller on SEV hardware. Until
226-
this is implemented, it is not possible for Nova to programmatically
227-
detect the correct value. As a short-term workaround, operators can
228-
optionally manually specify the upper limit of SEV guests for each
229-
compute host, via the new
230-
:oslo.config:option:`libvirt.num_memory_encrypted_guests`
231-
configuration option :ref:`described above
232-
<num_memory_encrypted_guests>`.
233-
234223
Permanent limitations
235224
~~~~~~~~~~~~~~~~~~~~~
236225

nova/conf/libvirt.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -854,14 +854,15 @@
854854
future. If the machine does not support memory encryption, the option
855855
will be ignored and inventory will be set to 0.
856856
857-
If the machine does support memory encryption, *for now* a value of
858-
``None`` means an effectively unlimited inventory, i.e. no limit will
859-
be imposed by Nova on the number of SEV guests which can be launched,
860-
even though the underlying hardware will enforce its own limit.
861-
However it is expected that in the future, auto-detection of the
862-
inventory from the hardware will become possible, at which point
863-
``None`` will cause auto-detection to automatically impose the correct
864-
limit.
857+
If the machine does support memory encryption and this option is not set,
858+
the driver detects maximum number of SEV guests from the libvirt API which
859+
is available since v8.0.0. Setting this option overrides the detected limit,
860+
unless the given value is not larger than the detected limit.
861+
862+
On the other hand, if an older version of libvirt is used, ``None`` means
863+
an effectively unlimited inventory, i.e. no limit will be imposed by Nova
864+
on the number of SEV guests which can be launched, even though the underlying
865+
hardware will enforce its own limit.
865866
866867
.. note::
867868

nova/tests/fixtures/libvirt.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,16 @@ def getCPUModelNames(self, arch):
19901990
_domain_capability_features_with_SEV_unsupported = \
19911991
_domain_capability_features_with_SEV.replace('yes', 'no')
19921992

1993+
_domain_capability_features_with_SEV_max_guests = ''' <features>
1994+
<gic supported='no'/>
1995+
<sev supported='yes'>
1996+
<cbitpos>47</cbitpos>
1997+
<reducedPhysBits>1</reducedPhysBits>
1998+
<maxGuests>100</maxGuests>
1999+
<maxESGuests>15</maxESGuests>
2000+
</sev>
2001+
</features>'''
2002+
19932003
def getCapabilities(self):
19942004
"""Return spoofed capabilities."""
19952005
numa_topology = self.host_info.numa_topology

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29368,7 +29368,7 @@ def test_get_mem_encrypted_slots_unsupported(self):
2936829368

2936929369
@mock.patch.object(vc, '_domain_capability_features',
2937029370
new=vc._domain_capability_features_with_SEV)
29371-
class TestLibvirtSEVSupported(TestLibvirtSEV):
29371+
class TestLibvirtSEVSupportedNoMaxGuests(TestLibvirtSEV):
2937229372
"""Libvirt driver tests for when AMD SEV support is present."""
2937329373
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
2937429374
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
@@ -29389,6 +29389,36 @@ def test_get_mem_encrypted_slots_config_zero_supported(self):
2938929389
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
2939029390

2939129391

29392+
@mock.patch.object(vc, '_domain_capability_features',
29393+
new=vc._domain_capability_features_with_SEV_max_guests)
29394+
class TestLibvirtSEVSupportedMaxGuests(TestLibvirtSEV):
29395+
"""Libvirt driver tests for when AMD SEV support is present."""
29396+
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
29397+
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
29398+
@mock.patch.object(libvirt_driver.LOG, 'warning')
29399+
def test_get_mem_encrypted_slots_no_override(self, mock_log):
29400+
self.assertEqual(100, self.driver._get_memory_encrypted_slots())
29401+
mock_log.assert_not_called()
29402+
29403+
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
29404+
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
29405+
@mock.patch.object(libvirt_driver.LOG, 'warning')
29406+
def test_get_mem_encrypted_slots_overlide_more(self, mock_log):
29407+
self.flags(num_memory_encrypted_guests=120, group='libvirt')
29408+
self.assertEqual(100, self.driver._get_memory_encrypted_slots())
29409+
mock_log.assert_called_with(
29410+
'Host is configured with libvirt.num_memory_encrypted_guests '
29411+
'set to %d, but supports only %d.', 120, 100)
29412+
29413+
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
29414+
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
29415+
@mock.patch.object(libvirt_driver.LOG, 'warning')
29416+
def test_get_mem_encrypted_slots_override_less(self, mock_log):
29417+
self.flags(num_memory_encrypted_guests=80, group='libvirt')
29418+
self.assertEqual(80, self.driver._get_memory_encrypted_slots())
29419+
mock_log.assert_not_called()
29420+
29421+
2939229422
class LibvirtPMEMNamespaceTests(test.NoDBTestCase):
2939329423

2939429424
def setUp(self):

nova/tests/unit/virt/libvirt/test_host.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,8 @@ def _test_get_domain_capabilities_sev(self, supported):
890890
if supported:
891891
self.assertEqual(47, sev.cbitpos)
892892
self.assertEqual(1, sev.reduced_phys_bits)
893+
self.assertIsNone(sev.max_guests)
894+
self.assertIsNone(sev.max_es_guests)
893895

894896
@mock.patch.object(
895897
fakelibvirt.virConnect, '_domain_capability_features', new=
@@ -904,6 +906,22 @@ def test_get_domain_capabilities_sev_unsupported(self):
904906
def test_get_domain_capabilities_sev_supported(self):
905907
self._test_get_domain_capabilities_sev(True)
906908

909+
@mock.patch.object(
910+
fakelibvirt.virConnect, '_domain_capability_features', new=
911+
fakelibvirt.virConnect._domain_capability_features_with_SEV_max_guests)
912+
def test_get_domain_capabilities_sev_max_guests(self):
913+
caps = self._test_get_domain_capabilities()
914+
self.assertEqual(vconfig.LibvirtConfigDomainCaps, type(caps))
915+
features = caps.features
916+
self.assertEqual(1, len(features))
917+
sev = features[0]
918+
self.assertEqual(vconfig.LibvirtConfigDomainCapsFeatureSev, type(sev))
919+
self.assertTrue(sev.supported)
920+
self.assertEqual(47, sev.cbitpos)
921+
self.assertEqual(1, sev.reduced_phys_bits)
922+
self.assertEqual(100, sev.max_guests)
923+
self.assertEqual(15, sev.max_es_guests)
924+
907925
@mock.patch.object(fakelibvirt.virConnect, "getHostname")
908926
def test_get_hostname_caching(self, mock_hostname):
909927
mock_hostname.return_value = "foo"

nova/virt/libvirt/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ def __init__(self, **kwargs):
310310
self.supported = False
311311
self.cbitpos = None
312312
self.reduced_phys_bits = None
313+
self.max_guests = None
314+
self.max_es_guests = None
313315

314316
def parse_dom(self, xmldoc):
315317
super(LibvirtConfigDomainCapsFeatureSev, self).parse_dom(xmldoc)
@@ -322,6 +324,10 @@ def parse_dom(self, xmldoc):
322324
self.reduced_phys_bits = int(c.text)
323325
elif c.tag == 'cbitpos':
324326
self.cbitpos = int(c.text)
327+
elif c.tag == 'maxGuests':
328+
self.max_guests = int(c.text)
329+
elif c.tag == 'maxESGuests':
330+
self.max_es_guests = int(c.text)
325331

326332

327333
class LibvirtConfigDomainCapsOS(LibvirtConfigObject):

nova/virt/libvirt/driver.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9009,33 +9009,30 @@ def _update_provider_tree_for_vpmems(self, provider_tree, nodename,
90099009
resources[rc].add(resource_obj)
90109010

90119011
def _get_memory_encrypted_slots(self):
9012-
slots = CONF.libvirt.num_memory_encrypted_guests
9012+
conf_slots = CONF.libvirt.num_memory_encrypted_guests
9013+
90139014
if not self._host.supports_amd_sev:
9014-
if slots and slots > 0:
9015+
if conf_slots and conf_slots > 0:
90159016
LOG.warning("Host is configured with "
90169017
"libvirt.num_memory_encrypted_guests set to "
9017-
"%d, but is not SEV-capable.", slots)
9018+
"%d, but is not SEV-capable.", conf_slots)
90189019
return 0
90199020

9020-
# NOTE(aspiers): Auto-detection of the number of available
9021-
# slots for AMD SEV is not yet possible, so honor the
9022-
# configured value, or impose no limit if this is not
9023-
# specified. This does incur a risk that if operators don't
9024-
# read the instructions and configure the maximum correctly,
9025-
# the maximum could be exceeded resulting in SEV guests
9026-
# failing at launch-time. However at least SEV guests will
9027-
# launch until the maximum, and when auto-detection code is
9028-
# added later, an upgrade will magically fix the issue.
9029-
#
9030-
# Note also that the configured value can be 0 on an
9031-
# SEV-capable host, since there might conceivably be good
9032-
# reasons for the operator to want to disable SEV even when
9033-
# it's available (e.g. due to performance impact, or
9034-
# implementation bugs which may surface later).
9035-
if slots is not None:
9036-
return slots
9037-
else:
9038-
return db_const.MAX_INT
9021+
slots = db_const.MAX_INT
9022+
9023+
# NOTE(tkajinam): Current nova supports SEV only so we ignore SEV-ES
9024+
if self._host.max_sev_guests is not None:
9025+
slots = self._host.max_sev_guests
9026+
9027+
if conf_slots is not None:
9028+
if conf_slots > slots:
9029+
LOG.warning("Host is configured with "
9030+
"libvirt.num_memory_encrypted_guests set to %d, "
9031+
"but supports only %d.", conf_slots, slots)
9032+
slots = min(slots, conf_slots)
9033+
9034+
LOG.debug("Available memory encrypted slots: %d", slots)
9035+
return slots
90399036

90409037
@property
90419038
def static_traits(self) -> ty.Dict[str, bool]:

nova/virt/libvirt/host.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ def __init__(self, uri, read_only=False,
162162
# kernel, QEMU, and/or libvirt. These are determined on demand and
163163
# memoized by various properties below
164164
self._supports_amd_sev: ty.Optional[bool] = None
165+
self._max_sev_guests: ty.Optional[int] = None
166+
self._max_sev_es_guests: ty.Optional[int] = None
165167
self._supports_uefi: ty.Optional[bool] = None
166168
self._supports_secure_boot: ty.Optional[bool] = None
167169

@@ -1836,11 +1838,29 @@ def supports_amd_sev(self) -> bool:
18361838
if feature_is_sev and feature.supported:
18371839
LOG.info("AMD SEV support detected")
18381840
self._supports_amd_sev = True
1841+
self._max_sev_guests = feature.max_guests
1842+
self._max_sev_es_guests = feature.max_es_guests
18391843
return self._supports_amd_sev
18401844

18411845
LOG.debug("No AMD SEV support detected for any (arch, machine_type)")
18421846
return self._supports_amd_sev
18431847

1848+
@property
1849+
def max_sev_guests(self) -> ty.Optional[int]:
1850+
"""Determine maximum number of guests with AMD SEV.
1851+
"""
1852+
if not self.supports_amd_sev:
1853+
return None
1854+
return self._max_sev_guests
1855+
1856+
@property
1857+
def max_sev_es_guests(self) -> ty.Optional[int]:
1858+
"""Determine maximum number of guests with AMD SEV-ES.
1859+
"""
1860+
if not self.supports_amd_sev:
1861+
return None
1862+
return self._max_sev_es_guests
1863+
18441864
@property
18451865
def supports_remote_managed_ports(self) -> bool:
18461866
"""Determine if the host supports remote managed ports.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features:
3+
- |
4+
Now the libvirt driver is capable to detect maximum number of guests with
5+
memory encrypted which can run concurrently in its compute host using
6+
the new fields in libvirt API available since version 8.0.0.

0 commit comments

Comments
 (0)