libvirt: Add Intel TDX host capability detection
Adds host capability detection for Intel TDX to the libvirt driver. Detection is performed using 2 mechanisms: libvirt domain capabilities and the capacity exposed by the cgroup misc controller (/sys/fs/cgroup/misc.capacity). The latter is used to report the number of available TDX keys. Intel TDX also requires CPU mode to be 'host-passthrough'. The config option [libvirt] 'num_intel_tdx_guests' can also be used to configure the maximum number of available TDX keys. This is to accomondate deployments where cgroup misc controller does not reflect the information by the host. Bumps os-traits dependency to version 3.8.0 to use Intel TDX trait. Bumps ImageMetaProps version to 1.44. Bumps ImageMetaPropsPayload version to 1.20. Partial-Implements: blueprint intel-tdx-libvirt-support Change-Id: I67315422eccc113390fedc905d3217a2d73367a1 Signed-off-by: antia <anton.iacobaeus@canarybit.eu>
This commit is contained in:
1 parent
07714e5030
commit
a69768e478
14 files changed
+403
-10
No files matched your search
@@ -4,5 +4,5 @@
|
||||
"hw_architecture": "x86_64"
|
||||
},
|
||||
"nova_object.name": "ImageMetaPropsPayload",
|
||||
"nova_object.version": "1.19"
|
||||
"nova_object.version": "1.20"
|
||||
}
|
||||
@@ -946,6 +946,27 @@ Related options:
|
||||
* ``my_ip``
|
||||
* ``live_migration_inbound_addr``
|
||||
|
||||
"""),
|
||||
cfg.IntOpt('num_intel_tdx_guests',
|
||||
default=None,
|
||||
min=0,
|
||||
help="""
|
||||
Maximum number of guests with Intel TDX (Intel Trust Domain Extensions) memory
|
||||
encryption that this compute host can run concurrently.
|
||||
|
||||
Intel TDX capacity is normally determined automatically by reading the number
|
||||
of available keys exposed by the kernel's misc cgroup controller (``tdx``
|
||||
entry in ``misc.capacity``), and this option does not need to be set in that
|
||||
case.
|
||||
|
||||
However cgroup detection is not always possible, for example when nova-compute
|
||||
is deployed inside a container without visibility of the host's cgroup
|
||||
hierarchy. If the cgroup value cannot be read, this option becomes the only
|
||||
source of the limit and **must** be set, or Nova will assume the host does not
|
||||
support Intel TDX.
|
||||
|
||||
If the cgroup value *can* be read and this option is also set, the lower
|
||||
of the two values is used.
|
||||
"""),
|
||||
]
|
||||
|
||||
|
||||
@@ -137,7 +137,8 @@ class ImageMetaPropsPayload(base.NotificationPayloadBase):
|
||||
# Version 1.17: Added 'hw_usb_model' and 'hw_redirected_usb_ports' fields
|
||||
# Version 1.18: Added 'hw_mem_encryption_model' field
|
||||
# Version 1.19: Added 'amd-sev-snp' as an option to hw_mem_encryption_model
|
||||
VERSION = '1.19'
|
||||
# Version 1.20: Added 'intel-tdx' as an option to hw_mem_encryption_model
|
||||
VERSION = '1.20'
|
||||
|
||||
# NOTE(efried): This logic currently relies on all of the fields of
|
||||
# ImageMetaProps being initialized with no arguments. See the docstring.
|
||||
|
||||
@@ -565,8 +565,9 @@ class MemEncryptionModel(BaseNovaEnum):
|
||||
AMD_SEV = "amd-sev"
|
||||
AMD_SEV_ES = "amd-sev-es"
|
||||
AMD_SEV_SNP = "amd-sev-snp"
|
||||
INTEL_TDX = "intel-tdx"
|
||||
|
||||
ALL = (AMD_SEV, AMD_SEV_ES, AMD_SEV_SNP)
|
||||
ALL = (AMD_SEV, AMD_SEV_ES, AMD_SEV_SNP, INTEL_TDX)
|
||||
|
||||
|
||||
class MaxPhyAddrMode(BaseNovaEnum):
|
||||
|
||||
@@ -207,15 +207,21 @@ class ImageMetaProps(base.NovaObject):
|
||||
# Version 1.41: Added 'hw_usb_model' and 'hw_redirected_usb_ports' fields
|
||||
# Version 1.42: Added 'hw_mem_encryption_model' field
|
||||
# Version 1.43: Added 'amd-sev-snp' as an option to hw_mem_encryption_model
|
||||
# Version 1.44: Added 'intel-tdx' as an option to hw_mem_encryption_model
|
||||
|
||||
# NOTE(efried): When bumping this version, the version of
|
||||
# ImageMetaPropsPayload must also be bumped. See its docstring for details.
|
||||
VERSION = '1.43'
|
||||
VERSION = '1.44'
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version): # noqa: C901
|
||||
super(ImageMetaProps, self).obj_make_compatible(primitive,
|
||||
target_version)
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version < (1, 44):
|
||||
base.raise_on_too_new_values(
|
||||
target_version, primitive,
|
||||
'hw_mem_encryption_model',
|
||||
(fields.MemEncryptionModel.INTEL_TDX,))
|
||||
if target_version < (1, 43):
|
||||
base.raise_on_too_new_values(
|
||||
target_version, primitive,
|
||||
|
||||
Vendored
+12
@@ -2475,6 +2475,18 @@ class Connection(object):
|
||||
</enum>
|
||||
</launchSecurity>'''
|
||||
|
||||
_domain_capability_features_with_TDX = '''\
|
||||
<features>
|
||||
<gic supported='no'/>
|
||||
<tdx supported='yes'/>
|
||||
</features>'''
|
||||
|
||||
_domain_capability_features_with_TDX_unsupported = '''\
|
||||
<features>
|
||||
<gic supported='no'/>
|
||||
<tdx supported='no'/>
|
||||
</features>'''
|
||||
|
||||
def getCapabilities(self):
|
||||
"""Return spoofed capabilities."""
|
||||
numa_topology = self.host_info.numa_topology
|
||||
|
||||
@@ -1258,7 +1258,7 @@ class TestInstanceNotificationSample(
|
||||
'nova_object.data': {},
|
||||
'nova_object.name': 'ImageMetaPropsPayload',
|
||||
'nova_object.namespace': 'nova',
|
||||
'nova_object.version': '1.19',
|
||||
'nova_object.version': '1.20',
|
||||
},
|
||||
'image.size': 58145823,
|
||||
'image.tags': [],
|
||||
@@ -1354,7 +1354,7 @@ class TestInstanceNotificationSample(
|
||||
'nova_object.data': {},
|
||||
'nova_object.name': 'ImageMetaPropsPayload',
|
||||
'nova_object.namespace': 'nova',
|
||||
'nova_object.version': '1.19',
|
||||
'nova_object.version': '1.20',
|
||||
},
|
||||
'image.size': 58145823,
|
||||
'image.tags': [],
|
||||
|
||||
@@ -385,7 +385,7 @@ notification_object_data = {
|
||||
# ImageMetaProps, so when you see a fail here for that reason, you must
|
||||
# *also* bump the version of ImageMetaPropsPayload. See its docstring for
|
||||
# more information.
|
||||
'ImageMetaPropsPayload': '1.19-473614bfe8a179dbe4f95334193334c3',
|
||||
'ImageMetaPropsPayload': '1.20-daa011f0b11624db935738ee0316ef78',
|
||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionPayload': '1.9-525dcf81b6e4592d935712a2675309dc',
|
||||
'InstanceActionRebuildNotification':
|
||||
|
||||
@@ -1105,7 +1105,7 @@ object_data = {
|
||||
'HyperVLiveMigrateData': '1.5-b424b27305f259fb3c15d720856585c7',
|
||||
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
|
||||
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
|
||||
'ImageMetaProps': '1.43-4a5ae3aad555458103fe5dbee297341d',
|
||||
'ImageMetaProps': '1.44-fd4f63a7cfab5a0ea0e034c40a554b26',
|
||||
'Instance': '2.8-2727dba5e4a078e6cc848c1f94f7eb24',
|
||||
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
|
||||
'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4',
|
||||
|
||||
@@ -3318,6 +3318,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
|
||||
@mock.patch('nova.virt.libvirt.utils.get_default_machine_type',
|
||||
new=mock.Mock(return_value='config-machine_type'))
|
||||
# the getDomainCapabilities test stub is called by memory encryption config
|
||||
# but it doesn't support the faked machine type used in this test
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver,
|
||||
'_get_mem_encryption_config', new=mock.Mock(return_value=None))
|
||||
def test_get_guest_config_records_machine_type_in_instance(self):
|
||||
# Assert that the config derived machine type is used when it
|
||||
# isn't present in the image_meta of an instance.
|
||||
@@ -6041,6 +6045,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
def test_get_guest_config_with_uefi(self):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
|
||||
image_meta = objects.ImageMeta.from_dict({
|
||||
@@ -6066,6 +6071,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
def test_get_guest_config_with_uefi_old_guest(self, mock_exists):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
|
||||
image_meta = objects.ImageMeta.from_dict({
|
||||
@@ -6106,6 +6112,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self, mock_exists):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
|
||||
image_meta = objects.ImageMeta.from_dict({
|
||||
@@ -6149,6 +6156,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self, mock_exists):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
|
||||
image_meta = objects.ImageMeta.from_dict({
|
||||
@@ -6190,6 +6198,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
def test_get_guest_config_with_uefi_and_stateless_firmware(self):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
|
||||
image_meta = objects.ImageMeta.from_dict({
|
||||
@@ -6221,6 +6230,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
drvr._host._supports_secure_boot = host_has_support
|
||||
|
||||
@@ -6261,6 +6271,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
drvr._host._supports_secure_boot = host_has_support
|
||||
|
||||
@@ -6298,6 +6309,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self, mock_exists, smm):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
drvr._host._supports_secure_boot = True
|
||||
|
||||
@@ -6349,6 +6361,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self, mock_exists):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = False
|
||||
drvr._host._supports_intel_tdx = False
|
||||
drvr._host._supports_uefi = True
|
||||
drvr._host._supports_secure_boot = False
|
||||
|
||||
@@ -32569,6 +32582,9 @@ class TestLibvirtSEVUnsupported(TestLibvirtSEV):
|
||||
},
|
||||
'amd_sev_snp': {
|
||||
'total': 0
|
||||
},
|
||||
'intel_tdx': {
|
||||
'total': 0
|
||||
}
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
@@ -32605,6 +32621,9 @@ class TestLibvirtSEVSupported(TestLibvirtSEV):
|
||||
'amd_sev_snp': {
|
||||
'total': 0
|
||||
},
|
||||
'intel_tdx': {
|
||||
'total': 0
|
||||
}
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
|
||||
|
||||
@@ -2555,6 +2555,186 @@ class TestLibvirtSEVSNPSupported(TestLibvirtSEV):
|
||||
inventories['amd_sev_snp'])
|
||||
|
||||
|
||||
class TestLibvirtTDX(test.NoDBTestCase):
|
||||
"""Libvirt host tests for Intel TDX support."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLibvirtTDX, self).setUp()
|
||||
|
||||
self.useFixture(nova_fixtures.LibvirtFixture())
|
||||
self.flags(cpu_mode='host-passthrough', group='libvirt')
|
||||
self.host = host.Host("qemu:///system")
|
||||
self.addCleanup(self.host.cleanup)
|
||||
self.host._delayed_executor = (
|
||||
thread_pool_factory.
|
||||
StaticallyDelayingCancellableTaskExecutorWrapper(
|
||||
delay=0.1, executor=thread_pool_factory.get_executor(
|
||||
thread_pool_factory.ExecutorType.DEFAULT)))
|
||||
|
||||
self.misc_capacity = None
|
||||
|
||||
def _exists(path):
|
||||
if path == '/sys/fs/cgroup/misc.capacity':
|
||||
return self.misc_capacity is not None
|
||||
elif path == '/sys/module/kvm_amd/parameters/sev':
|
||||
# Mock as absent so supports_mem_encryption() reports no SEV
|
||||
# support in TDX scenarios
|
||||
return None
|
||||
self.fail('Unexpected path is accessed by os.path: %s' % path)
|
||||
|
||||
self.mock_exists = self.useFixture(fixtures.MockPatch(
|
||||
'os.path.exists', side_effect=_exists)).mock
|
||||
|
||||
def _open(path):
|
||||
if path == '/sys/fs/cgroup/misc.capacity':
|
||||
return io.StringIO(self.misc_capacity)
|
||||
self.fail('Unexpected path is accessed by open: %s' % path)
|
||||
|
||||
self.mock_open = self.useFixture(fixtures.MockPatch(
|
||||
'builtins.open', side_effect=_open)).mock
|
||||
|
||||
|
||||
class TestLibvirtTDXUnsupported(TestLibvirtTDX):
|
||||
def test_unsupported_without_feature(self):
|
||||
self.assertFalse(self.host.supports_intel_tdx)
|
||||
self.assertFalse(self.host.supports_mem_encryption)
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_unsupported_without_host_passthrough(self):
|
||||
self.flags(cpu_mode='host-model', group='libvirt')
|
||||
self.assertFalse(self.host.supports_intel_tdx)
|
||||
self.assertFalse(self.host.supports_mem_encryption)
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX_unsupported)
|
||||
def test_unsupported_with_feature(self):
|
||||
self.assertFalse(self.host.supports_intel_tdx)
|
||||
self.assertFalse(self.host.supports_mem_encryption)
|
||||
|
||||
def test_non_x86_architecture(self):
|
||||
fake_caps_xml = '''
|
||||
<capabilities>
|
||||
<host>
|
||||
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
||||
<cpu>
|
||||
<arch>aarch64</arch>
|
||||
</cpu>
|
||||
</host>
|
||||
</capabilities>'''
|
||||
with mock.patch.object(fakelibvirt.virConnect, 'getCapabilities',
|
||||
return_value=fake_caps_xml):
|
||||
self.assertFalse(self.host.supports_intel_tdx)
|
||||
self.assertFalse(self.host.supports_mem_encryption)
|
||||
|
||||
def test_slots(self):
|
||||
self.assertEqual(0, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
def test_slots_with_config_set(self):
|
||||
self.flags(num_intel_tdx_guests=63, group='libvirt')
|
||||
self.assertEqual(0, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
def test_inventory_traits(self):
|
||||
self.assertEqual(['HW_CPU_X86_INTEL_TDX'],
|
||||
self.host._get_mem_encryption_traits_intel_tdx())
|
||||
|
||||
def test_inventory(self):
|
||||
inventories = self.host.get_mem_encryption_inventories()
|
||||
self.assertIn('intel_tdx', inventories)
|
||||
self.assertEqual({'supported': False}, inventories['intel_tdx'])
|
||||
|
||||
|
||||
class TestLibvirtTDXSupported(TestLibvirtTDX):
|
||||
"""Libvirt driver tests for when Intel TDX support is present."""
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_supported_with_feature(self):
|
||||
self.misc_capacity = "tdx 0\n"
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_with_capacity_and_config_absent(self):
|
||||
self.misc_capacity = "tdx 63\n"
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(63, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_with_capacity_and_config_less(self):
|
||||
self.misc_capacity = "tdx 63\n"
|
||||
self.flags(num_intel_tdx_guests=1, group='libvirt')
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(1, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_with_capacity_and_config_equal(self):
|
||||
self.misc_capacity = "tdx 63\n"
|
||||
self.flags(num_intel_tdx_guests=63, group='libvirt')
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(63, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_with_capacity_and_config_more(self):
|
||||
self.misc_capacity = "tdx 63\n"
|
||||
self.flags(num_intel_tdx_guests=100, group='libvirt')
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(100, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_capacity_file_and_config_absent_when_supported(self):
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(0, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_capacity_file_absent_and_config_zero_when_supported(self):
|
||||
self.flags(num_intel_tdx_guests=0, group='libvirt')
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(0, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_slots_capacity_file_absent_and_config_set_when_supported(self):
|
||||
self.flags(num_intel_tdx_guests=63, group='libvirt')
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
self.assertEqual(63, self.host._get_mem_encryption_slots_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_inventory_traits(self):
|
||||
self.assertEqual(['HW_CPU_X86_INTEL_TDX'],
|
||||
self.host._get_mem_encryption_traits_intel_tdx())
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
new=vc._domain_capability_features_with_TDX)
|
||||
def test_inventory_supported(self):
|
||||
self.misc_capacity = "tdx 100\n"
|
||||
self.assertTrue(self.host.supports_intel_tdx)
|
||||
self.assertTrue(self.host.supports_mem_encryption)
|
||||
inventories = self.host.get_mem_encryption_inventories()
|
||||
self.assertIn('intel_tdx', inventories)
|
||||
self.assertEqual(
|
||||
{
|
||||
'supported': True,
|
||||
'total': 100,
|
||||
'traits': ['HW_CPU_X86_INTEL_TDX'],
|
||||
},
|
||||
inventories['intel_tdx'])
|
||||
|
||||
|
||||
class LibvirtTpoolProxyTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
if utils.concurrency_mode_threading():
|
||||
|
||||
@@ -337,6 +337,8 @@ class LibvirtConfigDomainCapsFeatures(LibvirtConfigObject):
|
||||
feature = None
|
||||
if c.tag == "sev":
|
||||
feature = LibvirtConfigDomainCapsFeatureSev()
|
||||
if c.tag == "tdx":
|
||||
feature = LibvirtConfigDomainCapsFeatureTDX()
|
||||
if c.tag == "launchSecurity":
|
||||
feature = LibvirtConfigDomainCapsFeatureLaunchSecurity()
|
||||
if feature:
|
||||
@@ -397,6 +399,19 @@ class LibvirtConfigDomainCapsFeatureLaunchSecurity(LibvirtConfigObject):
|
||||
child.text for child in c if child.tag == 'value']
|
||||
|
||||
|
||||
class LibvirtConfigDomainCapsFeatureTDX(LibvirtConfigObject):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(root_name='tdx', **kwargs)
|
||||
self.supported = False
|
||||
|
||||
def parse_dom(self, xmldoc):
|
||||
super().parse_dom(xmldoc)
|
||||
|
||||
if xmldoc.get('supported') == 'yes':
|
||||
self.supported = True
|
||||
|
||||
|
||||
class LibvirtConfigDomainCapsOS(LibvirtConfigObject):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
+139
-1
@@ -82,6 +82,8 @@ SEV_KERNEL_PARAM_FILE = '/sys/module/kvm_amd/parameters/%s'
|
||||
|
||||
MIN_QEMU_SEV_ES_VERSION = (8, 0, 0)
|
||||
|
||||
MISC_CONTROL_GROUP_CAPACITY_FILE = '/sys/fs/cgroup/misc.capacity'
|
||||
|
||||
|
||||
class LibvirtEventHandler:
|
||||
def __init__(self, conn_event_handler=None, lifecycle_event_handler=None):
|
||||
@@ -328,6 +330,8 @@ class Host(object):
|
||||
self._supports_amd_sev_snp: bool | None = None
|
||||
self._max_sev_guests: int = 0
|
||||
self._max_sev_es_guests: int = 0
|
||||
self._supports_intel_tdx: bool | None = None
|
||||
self._max_intel_tdx_guests: int | None = None
|
||||
self._supports_uefi: bool | None = None
|
||||
self._supports_secure_boot: bool | None = None
|
||||
|
||||
@@ -2151,6 +2155,134 @@ class Host(object):
|
||||
self._supports_amd_sev_snp = False
|
||||
return self._supports_amd_sev_snp
|
||||
|
||||
@property
|
||||
def supports_intel_tdx(self) -> bool:
|
||||
"""Determine if the host supports Intel TDX for guests.
|
||||
|
||||
Returns a boolean indicating whether Intel Trust Domain Extensions
|
||||
(Intel TDX) is supported. This is conditional on support in the
|
||||
hardware, kernel, qemu, and libvirt.
|
||||
|
||||
CPU mode also has to be configured to host-passthrough, since Intel TDX
|
||||
enabled guests require it.
|
||||
|
||||
This checks whether the feature is supported by *any* machine type.
|
||||
This is only used for trait-reporting purposes and a machine
|
||||
type-specific check should be used when creating guests.
|
||||
"""
|
||||
if self._supports_intel_tdx is not None:
|
||||
return self._supports_intel_tdx
|
||||
|
||||
self._supports_intel_tdx = False
|
||||
|
||||
caps = self.get_capabilities()
|
||||
if caps.host.cpu.arch != fields.Architecture.X86_64:
|
||||
return self._supports_intel_tdx
|
||||
|
||||
# NOTE(antia): This effectively requires cpu_mode to be explicitly
|
||||
# configured to "host-passthrough" to enable Intel TDX support. TDX
|
||||
# requires the guest to see the real host CPU, so host-passthrough is a
|
||||
# hard prerequisite.
|
||||
cpu_mode = CONF.libvirt.cpu_mode
|
||||
if cpu_mode != "host-passthrough":
|
||||
LOG.debug(
|
||||
"Skipping Intel TDX detection: cpu_mode is not "
|
||||
"'host-passthrough', which is required.",
|
||||
)
|
||||
return self._supports_intel_tdx
|
||||
|
||||
domain_caps = self.get_domain_capabilities()
|
||||
for arch in domain_caps:
|
||||
for machine_type in domain_caps[arch]:
|
||||
LOG.debug(
|
||||
"Checking TDX support for arch %s and machine type %s",
|
||||
arch,
|
||||
machine_type,
|
||||
)
|
||||
for feature in domain_caps[arch][machine_type].features:
|
||||
feature_is_tdx = isinstance(
|
||||
feature, vconfig.LibvirtConfigDomainCapsFeatureTDX
|
||||
)
|
||||
if feature_is_tdx and feature.supported:
|
||||
LOG.info("Intel TDX support detected")
|
||||
self._supports_intel_tdx = True
|
||||
return self._supports_intel_tdx
|
||||
|
||||
LOG.debug("No Intel TDX support detected for any (arch, machine_type)")
|
||||
return self._supports_intel_tdx
|
||||
|
||||
def _get_cgroup_tdx_capacity(self):
|
||||
"""Read kernel misc control group for maximum Intel TDX guests.
|
||||
|
||||
The misc control group is a generic interface for any resource that may
|
||||
need limiting, it is not specific to Intel TDX. Only the capacity part
|
||||
is used.
|
||||
https://lwn.net/Articles/856438/
|
||||
"""
|
||||
capacity_file = MISC_CONTROL_GROUP_CAPACITY_FILE
|
||||
|
||||
if not os.path.exists(capacity_file):
|
||||
LOG.debug("%s does not exist", capacity_file)
|
||||
return None
|
||||
|
||||
with open(capacity_file) as f:
|
||||
content = f.read()
|
||||
LOG.debug("%s contains [%s]", capacity_file, content)
|
||||
for line in content.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) == 2 and parts[0] == 'tdx':
|
||||
try:
|
||||
return int(parts[1])
|
||||
except ValueError:
|
||||
LOG.warning(
|
||||
"Failed to parse TDX capacity from %s: "
|
||||
"unexpected value [%s]", capacity_file, parts[1])
|
||||
return None
|
||||
LOG.warning("No TDX entry found in %s", capacity_file)
|
||||
return None
|
||||
|
||||
def _get_tdx_capacity(self) -> int:
|
||||
"""Determine the maximum number of Intel TDX guests this host can run
|
||||
concurrently.
|
||||
|
||||
The primary source is the kernel misc control group (see
|
||||
_get_cgroup_tdx_capacity). In case this is unavailable
|
||||
[libvirt] num_tdx_guests becomes the only source and must be set.
|
||||
If both are set then the smallest of the two are used.
|
||||
"""
|
||||
cgroup_capacity = self._get_cgroup_tdx_capacity()
|
||||
conf_capacity = CONF.libvirt.num_intel_tdx_guests
|
||||
|
||||
if cgroup_capacity is None:
|
||||
if conf_capacity is None:
|
||||
LOG.warning(
|
||||
"Unable to determine Intel TDX capacity from the misc "
|
||||
"cgroup, and libvirt.num_intel_tdx_guests is not set. "
|
||||
"Assuming this host does not support Intel TDX.")
|
||||
return 0
|
||||
return conf_capacity
|
||||
|
||||
if conf_capacity is not None:
|
||||
if conf_capacity > cgroup_capacity:
|
||||
LOG.warning(
|
||||
"Host is configured with libvirt.num_intel_tdx_guests "
|
||||
"set to %d, but only supports %d Intel TDX guests "
|
||||
"according to the misc cgroup.",
|
||||
conf_capacity, cgroup_capacity)
|
||||
return conf_capacity
|
||||
|
||||
return cgroup_capacity
|
||||
|
||||
@property
|
||||
def max_intel_tdx_guests(self) -> int:
|
||||
"""Determine maximum number of guests with Intel TDX.
|
||||
"""
|
||||
if not self.supports_intel_tdx:
|
||||
return 0
|
||||
if self._max_intel_tdx_guests is None:
|
||||
self._max_intel_tdx_guests = self._get_tdx_capacity()
|
||||
return self._max_intel_tdx_guests
|
||||
|
||||
@property
|
||||
def supports_mem_encryption(self) -> bool:
|
||||
"""Determine if the host supports memory encryption for guests.
|
||||
@@ -2163,7 +2295,7 @@ class Host(object):
|
||||
is supported.
|
||||
"""
|
||||
|
||||
return self.supports_amd_sev
|
||||
return self.supports_amd_sev or self.supports_intel_tdx
|
||||
|
||||
def get_mem_encryption_inventories(self) -> dict[str, ty.Any]:
|
||||
"""Return a dictionary of memory encryption information.
|
||||
@@ -2220,6 +2352,12 @@ class Host(object):
|
||||
def _get_mem_encryption_traits_amd_sev_snp(self) -> list[str]:
|
||||
return [ot.HW_CPU_X86_AMD_SEV_SNP]
|
||||
|
||||
def _get_mem_encryption_slots_intel_tdx(self) -> int:
|
||||
return self.max_intel_tdx_guests
|
||||
|
||||
def _get_mem_encryption_traits_intel_tdx(self) -> list[str]:
|
||||
return [ot.HW_CPU_X86_INTEL_TDX]
|
||||
|
||||
@property
|
||||
def supports_remote_managed_ports(self) -> bool:
|
||||
"""Determine if the host supports remote managed ports.
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ psutil>=3.2.2 # BSD
|
||||
oslo.versionedobjects>=1.35.0 # Apache-2.0
|
||||
os-brick>=6.10.0 # Apache-2.0
|
||||
os-resource-classes>=1.1.0 # Apache-2.0
|
||||
os-traits>=3.7.0 # Apache-2.0
|
||||
os-traits>=3.8.0 # Apache-2.0
|
||||
os-vif>=3.1.0 # Apache-2.0
|
||||
castellan>=0.16.0 # Apache-2.0
|
||||
microversion-parse>=0.2.1 # Apache-2.0
|
||||
|
||||
Reference in new issue
Block a user