Merge "Migrate MEM_ENCRYPTION_CONTEXT from root provider"
This commit is contained in:
@@ -30,15 +30,13 @@ CONF = conf.CONF
|
||||
|
||||
class LibvirtReportTraitsTestBase(
|
||||
integrated_helpers.LibvirtProviderUsageBaseTestCase):
|
||||
pass
|
||||
|
||||
def assertMemEncryptionSlotsEqual(self, slots):
|
||||
inventory = self._get_provider_inventory(self.host_uuid)
|
||||
def assertMemEncryptionSlotsEqual(self, rp_uuid, slots):
|
||||
inventory = self._get_provider_inventory(rp_uuid)
|
||||
if slots == 0:
|
||||
self.assertNotIn(orc.MEM_ENCRYPTION_CONTEXT, inventory)
|
||||
else:
|
||||
self.assertEqual(
|
||||
inventory[orc.MEM_ENCRYPTION_CONTEXT],
|
||||
{
|
||||
'total': slots,
|
||||
'min_unit': 1,
|
||||
@@ -46,9 +44,16 @@ class LibvirtReportTraitsTestBase(
|
||||
'step_size': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
'reserved': 0,
|
||||
}
|
||||
},
|
||||
inventory[orc.MEM_ENCRYPTION_CONTEXT]
|
||||
)
|
||||
|
||||
def _get_amd_sev_rps(self):
|
||||
root_rp = self._get_resource_provider_by_uuid(self.host_uuid)
|
||||
rps = self._get_all_rps_in_a_tree(self.host_uuid)
|
||||
return [rp for rp in rps
|
||||
if rp['name'] == '%s_amd_sev' % root_rp['name']]
|
||||
|
||||
|
||||
class LibvirtReportTraitsTests(LibvirtReportTraitsTestBase):
|
||||
# These must match the capabilities in
|
||||
@@ -143,8 +148,10 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase):
|
||||
|
||||
traits = self._get_provider_traits(self.host_uuid)
|
||||
self.assertNotIn(sev_trait, traits)
|
||||
self.assertMemEncryptionSlotsEqual(self.host_uuid, 0)
|
||||
|
||||
self.assertMemEncryptionSlotsEqual(0)
|
||||
sev_rps = self._get_amd_sev_rps()
|
||||
self.assertEqual(0, len(sev_rps))
|
||||
|
||||
# Now simulate the host gaining SEV functionality. Here we
|
||||
# simulate a kernel update or reconfiguration which causes the
|
||||
@@ -178,13 +185,21 @@ class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase):
|
||||
self.compute.driver._static_traits = None
|
||||
self._run_periodics()
|
||||
|
||||
traits = self._get_provider_traits(self.host_uuid)
|
||||
self.assertIn(sev_trait, traits)
|
||||
|
||||
# Sanity check that we've still got the trait globally.
|
||||
self.assertIn(sev_trait, self._get_all_traits())
|
||||
|
||||
self.assertMemEncryptionSlotsEqual(db_const.MAX_INT)
|
||||
# sev capabilities are managed by sub rp and are not present in
|
||||
# root rp
|
||||
traits = self._get_provider_traits(self.host_uuid)
|
||||
self.assertNotIn(sev_trait, traits)
|
||||
self.assertMemEncryptionSlotsEqual(self.host_uuid, 0)
|
||||
|
||||
sev_rps = self._get_amd_sev_rps()
|
||||
self.assertEqual(1, len(sev_rps))
|
||||
sev_rp_uuid = sev_rps[0]['uuid']
|
||||
sev_rp_traits = self._get_provider_traits(sev_rp_uuid)
|
||||
self.assertIn(sev_trait, sev_rp_traits)
|
||||
self.assertMemEncryptionSlotsEqual(sev_rp_uuid, db_const.MAX_INT)
|
||||
|
||||
|
||||
class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
||||
@@ -221,10 +236,17 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
||||
global_traits = self._get_all_traits()
|
||||
self.assertIn(sev_trait, global_traits)
|
||||
|
||||
# sev capabilities are managed by sub rp and are not present in root rp
|
||||
traits = self._get_provider_traits(self.host_uuid)
|
||||
self.assertIn(sev_trait, traits)
|
||||
self.assertNotIn(sev_trait, traits)
|
||||
self.assertMemEncryptionSlotsEqual(self.host_uuid, 0)
|
||||
|
||||
self.assertMemEncryptionSlotsEqual(16)
|
||||
sev_rps = self._get_amd_sev_rps()
|
||||
self.assertEqual(1, len(sev_rps))
|
||||
sev_rp_uuid = sev_rps[0]['uuid']
|
||||
sev_rp_traits = self._get_provider_traits(sev_rp_uuid)
|
||||
self.assertIn(sev_trait, sev_rp_traits)
|
||||
self.assertMemEncryptionSlotsEqual(sev_rp_uuid, 16)
|
||||
|
||||
# Now simulate the host losing SEV functionality. Here we
|
||||
# simulate a kernel downgrade or reconfiguration which causes
|
||||
@@ -247,10 +269,14 @@ class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
|
||||
self.compute.driver._static_traits = None
|
||||
self._run_periodics()
|
||||
|
||||
traits = self._get_provider_traits(self.host_uuid)
|
||||
self.assertNotIn(sev_trait, traits)
|
||||
|
||||
# Sanity check that we've still got the trait globally.
|
||||
self.assertIn(sev_trait, self._get_all_traits())
|
||||
|
||||
self.assertMemEncryptionSlotsEqual(0)
|
||||
traits = self._get_provider_traits(self.host_uuid)
|
||||
self.assertNotIn(sev_trait, traits)
|
||||
|
||||
# NOTE(tkajinam): Currently the sev rp is not deleted after sev
|
||||
# support is turned off. This follows the existing behavior for
|
||||
# other resources such as vGPU.
|
||||
# sev_rps = self._get_amd_sev_rps()
|
||||
# self.assertEqual(0, len(sev_rps))
|
||||
|
@@ -11,11 +11,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import io
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils.fixture import uuidsentinel
|
||||
|
||||
from nova import context
|
||||
from nova import objects
|
||||
@@ -236,3 +238,100 @@ class VGPUReshapeTests(base.ServersTestBase):
|
||||
self.assertEqual(
|
||||
{'VGPU': 1},
|
||||
allocations[gpu_rp_uuid]['resources'])
|
||||
|
||||
|
||||
class SevResphapeTests(base.ServersTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
admin_context = context.get_admin_context()
|
||||
hw_mem_enc_image = copy.deepcopy(self.glance.image1)
|
||||
hw_mem_enc_image['id'] = uuidsentinel.mem_enc_image_id
|
||||
hw_mem_enc_image['properties']['hw_machine_type'] = 'q35'
|
||||
hw_mem_enc_image['properties']['hw_firmware_type'] = 'uefi'
|
||||
hw_mem_enc_image['properties']['hw_mem_encryption'] = True
|
||||
self.glance.create(admin_context, hw_mem_enc_image)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._guest_configure_sev')
|
||||
def test_create_servers_with_amd_sev(self, mock_configure_sev):
|
||||
self.hostname = self.start_compute(
|
||||
hostname='compute1',
|
||||
)
|
||||
self.compute = self.computes[self.hostname]
|
||||
self.flags(num_memory_encrypted_guests=16, group='libvirt')
|
||||
|
||||
# create the MEM_ENCRYPTION_CONTEXT resource in placement manually,
|
||||
# to simulate the old layout.
|
||||
compute_rp_uuid = self.placement.get(
|
||||
'/resource_providers?name=compute1').body[
|
||||
'resource_providers'][0]['uuid']
|
||||
inventories = self.placement.get(
|
||||
'/resource_providers/%s/inventories' % compute_rp_uuid).body
|
||||
inventories['inventories']['MEM_ENCRYPTION_CONTEXT'] = {
|
||||
'allocation_ratio': 1.0,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'step_size': 1,
|
||||
'total': 16}
|
||||
self.placement.put(
|
||||
'/resource_providers/%s/inventories' % compute_rp_uuid,
|
||||
inventories)
|
||||
|
||||
# create a server before reshape
|
||||
with mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
|
||||
'update_provider_tree'):
|
||||
pre_server = self._create_server(
|
||||
image_uuid=uuidsentinel.mem_enc_image_id)
|
||||
self.addCleanup(self._delete_server, pre_server)
|
||||
|
||||
# verify that the inventory, usages and allocation are correct before
|
||||
# the reshape
|
||||
compute_inventory = self.placement.get(
|
||||
'/resource_providers/%s/inventories' % compute_rp_uuid).body[
|
||||
'inventories']
|
||||
self.assertEqual(
|
||||
16, compute_inventory['MEM_ENCRYPTION_CONTEXT']['total'])
|
||||
compute_usages = self.placement.get(
|
||||
'/resource_providers/%s/usages' % compute_rp_uuid).body[
|
||||
'usages']
|
||||
self.assertEqual(1, compute_usages['MEM_ENCRYPTION_CONTEXT'])
|
||||
|
||||
# restart the compute service to trigger reshape
|
||||
with mock.patch('nova.virt.libvirt.host.Host.supports_amd_sev',
|
||||
return_value=True):
|
||||
self.compute = self.restart_compute_service(self.hostname)
|
||||
|
||||
# verify that the inventory, usages and allocation are correct after
|
||||
# the reshape
|
||||
compute_inventory = self.placement.get(
|
||||
'/resource_providers/%s/inventories' % compute_rp_uuid).body[
|
||||
'inventories']
|
||||
self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_inventory)
|
||||
compute_usages = self.placement.get(
|
||||
'/resource_providers/%s/usages' % compute_rp_uuid).body[
|
||||
'usages']
|
||||
self.assertNotIn('MEM_ENCRYPTION_CONTEXT', compute_usages)
|
||||
|
||||
sev_rp_uuid = self.placement.get(
|
||||
'/resource_providers?name=compute1_amd_sev').body[
|
||||
'resource_providers'][0]['uuid']
|
||||
sev_inventory = self.placement.get(
|
||||
'/resource_providers/%s/inventories' % sev_rp_uuid).body[
|
||||
'inventories']
|
||||
self.assertEqual(
|
||||
16, sev_inventory['MEM_ENCRYPTION_CONTEXT']['total'])
|
||||
sev_usages = self.placement.get(
|
||||
'/resource_providers/%s/usages' % sev_rp_uuid).body[
|
||||
'usages']
|
||||
self.assertEqual(1, sev_usages['MEM_ENCRYPTION_CONTEXT'])
|
||||
|
||||
# create a new server after reshape
|
||||
post_server = self._create_server(
|
||||
image_uuid=uuidsentinel.mem_enc_image_id)
|
||||
self.addCleanup(self._delete_server, post_server)
|
||||
|
||||
compute_usages = self.placement.get(
|
||||
'/resource_providers/%s/usages' % sev_rp_uuid).body[
|
||||
'usages']
|
||||
self.assertEqual(2, compute_usages['MEM_ENCRYPTION_CONTEXT'])
|
||||
|
@@ -23130,6 +23130,9 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
mock_gpu_invs.return_value = gpu_inventory_dicts
|
||||
# Use an empty list for vpmems.
|
||||
self.driver._vpmems_by_rc = {'CUSTOM_PMEM_NAMESPACE_4GB': []}
|
||||
# Use total=0 for MEM_ENCRYPTION_CONTEXT
|
||||
self.driver._host._supports_amd_sev = True
|
||||
self.driver._host._max_sev_guests = 0
|
||||
# Before we update_provider_tree, we have 2 providers from setUp():
|
||||
# self.cn_rp and self.shared_rp and they are both empty {}.
|
||||
self.assertEqual(2, len(self.pt.get_provider_uuids()))
|
||||
@@ -23251,6 +23254,35 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
self.assertEqual(expected_resources,
|
||||
self.pt.data(self.cn_rp['uuid']).resources)
|
||||
|
||||
def test_update_provider_tree_with_memory_encryption(self):
|
||||
self.driver._host._supports_amd_sev = True
|
||||
self.driver._host._max_sev_guests = 16
|
||||
self._test_update_provider_tree()
|
||||
inventory = self._get_inventory()
|
||||
# root compute node provider inventory is unchanged
|
||||
self.assertEqual(inventory,
|
||||
(self.pt.data(self.cn_rp['uuid'])).inventory)
|
||||
# We should have new sev child providers in the tree under the
|
||||
# compute node root provider.
|
||||
compute_node_tree_uuids = self.pt.get_provider_uuids(
|
||||
self.cn_rp['name'])
|
||||
self.assertEqual(2, len(compute_node_tree_uuids))
|
||||
sev_rp_uuid = compute_node_tree_uuids[1]
|
||||
sev_provider_data = self.pt.data(sev_rp_uuid)
|
||||
self.assertEqual('%s_amd_sev' % self.cn_rp['name'],
|
||||
sev_provider_data.name)
|
||||
self.assertEqual({
|
||||
orc.MEM_ENCRYPTION_CONTEXT: {
|
||||
'total': 16,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0
|
||||
}
|
||||
}, sev_provider_data.inventory)
|
||||
self.assertEqual({ot.HW_CPU_X86_AMD_SEV}, sev_provider_data.traits)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_local_gb_info',
|
||||
new=mock.Mock(return_value={'total': disk_gb}))
|
||||
@mock.patch('nova.virt.libvirt.host.Host.get_memory_mb_total',
|
||||
@@ -23551,6 +23583,170 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
self.assertIn('Unexpected VGPU resource allocation on provider %s'
|
||||
% uuids.other_rp, str(ex))
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
|
||||
'_get_cpu_feature_traits',
|
||||
new=mock.Mock(return_value=cpu_traits))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_local_gb_info',
|
||||
new=mock.Mock(return_value={'total': disk_gb}))
|
||||
@mock.patch('nova.virt.libvirt.host.Host.get_memory_mb_total',
|
||||
new=mock.Mock(return_value=memory_mb))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_pcpu_available',
|
||||
new=mock.Mock(return_value=range(pcpus)))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_vcpu_available',
|
||||
new=mock.Mock(return_value=range(vcpus)))
|
||||
def test_update_provider_tree_for_memory_encryption_reshape(self):
|
||||
self.driver._host._supports_amd_sev = True
|
||||
self.driver._host._max_sev_guests = 16
|
||||
# First create a provider tree with MEM_ENCRYPTION_CONTEXT inventory on
|
||||
# the root node provider.
|
||||
inventory = self._get_inventory()
|
||||
sev_inventory = {
|
||||
orc.MEM_ENCRYPTION_CONTEXT: {
|
||||
'total': 16,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0
|
||||
}
|
||||
}
|
||||
inventory.update(sev_inventory)
|
||||
self.pt.update_inventory(self.cn_rp['uuid'], inventory)
|
||||
# Call update_provider_tree which will raise ReshapeNeeded because
|
||||
# there is MEM_ENCRYPTION_CONTEXT on the root node provider
|
||||
self.assertRaises(exception.ReshapeNeeded,
|
||||
self.driver.update_provider_tree,
|
||||
self.pt, self.cn_rp['name'])
|
||||
# Now make up some fake allocations to pass back to the upt method
|
||||
# for the reshape
|
||||
allocations = {
|
||||
uuids.consumer1: {
|
||||
'allocations': {
|
||||
# This consumer has MEM_ENCRYPTION_CONTEXT allocations on
|
||||
# the root node provider and *should* be changed.
|
||||
self.cn_rp['uuid']: {
|
||||
'resources': {
|
||||
orc.MEMORY_MB: 512,
|
||||
orc.VCPU: 2,
|
||||
orc.MEM_ENCRYPTION_CONTEXT: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
uuids.consumer2: {
|
||||
'allocations': {
|
||||
# This consumer has no MEM_ENCRYPTION_CONTEXT allocations
|
||||
# on the root provider *should not* be changed.
|
||||
self.cn_rp['uuid']: {
|
||||
'resources': {
|
||||
orc.MEMORY_MB: 512,
|
||||
orc.VCPU: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
original_allocations = copy.deepcopy(allocations)
|
||||
# Initiate the reshape
|
||||
self.driver.update_provider_tree(
|
||||
self.pt, self.cn_rp['name'], allocations=allocations)
|
||||
# We should have one SEV child provider in the tree under the compute
|
||||
# node root provider.
|
||||
compute_node_tree_uuids = self.pt.get_provider_uuids(
|
||||
self.cn_rp['name'])
|
||||
self.assertEqual(2, len(compute_node_tree_uuids))
|
||||
# The SEV provider should be the 2nd UUID in the list
|
||||
sev_rp_uuid = compute_node_tree_uuids[1]
|
||||
# The MEM_ENCRYPTION_CONTEXT inventory should be on the SEV child
|
||||
# provider
|
||||
sev_provider_data = self.pt.data(sev_rp_uuid)
|
||||
self.assertEqual('%s_amd_sev' % self.cn_rp['name'],
|
||||
sev_provider_data.name)
|
||||
self.assertEqual({
|
||||
orc.MEM_ENCRYPTION_CONTEXT: {
|
||||
'total': 16,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0
|
||||
}
|
||||
}, sev_provider_data.inventory)
|
||||
# Make sure the child provider has the SEV trait
|
||||
self.assertEqual({ot.HW_CPU_X86_AMD_SEV}, sev_provider_data.traits)
|
||||
|
||||
# The compute node root provider should not have MEM_ENCRYPTION_CONTEXT
|
||||
# inventory.
|
||||
del inventory[orc.MEM_ENCRYPTION_CONTEXT]
|
||||
self.assertEqual(inventory, self.pt.data(self.cn_rp['uuid']).inventory)
|
||||
# consumer1 should now have allocations against two providers,
|
||||
# MEMORY_MB on the root compute node provider and
|
||||
# MEM_ENCRYPTION_CONTEXT on the child provider.
|
||||
consumer1_allocs = allocations[uuids.consumer1]['allocations']
|
||||
self.assertEqual(2, len(consumer1_allocs))
|
||||
self.assertEqual({orc.MEMORY_MB: 512, orc.VCPU: 2},
|
||||
consumer1_allocs[self.cn_rp['uuid']]['resources'])
|
||||
# Make sure the MEM_ENCRYPTION_CONTEXT allocation moved to
|
||||
# the corresponding child RP
|
||||
self.assertEqual({orc.MEM_ENCRYPTION_CONTEXT: 1},
|
||||
consumer1_allocs[sev_rp_uuid]['resources'])
|
||||
# The allocations on consumer2 should be unchanged.
|
||||
self.assertEqual(original_allocations[uuids.consumer2],
|
||||
allocations[uuids.consumer2])
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
|
||||
'_get_cpu_feature_traits',
|
||||
new=mock.Mock(return_value=cpu_traits))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_local_gb_info',
|
||||
new=mock.Mock(return_value={'total': disk_gb}))
|
||||
@mock.patch('nova.virt.libvirt.host.Host.get_memory_mb_total',
|
||||
new=mock.Mock(return_value=memory_mb))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_pcpu_available',
|
||||
new=mock.Mock(return_value=range(pcpus)))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_vcpu_available',
|
||||
new=mock.Mock(return_value=range(vcpus)))
|
||||
def test_update_provider_tree_for_memory_encryption_reshape_fails(self):
|
||||
self.driver._host._supports_amd_sev = True
|
||||
self.driver._host._max_sev_guests = 16
|
||||
# First create a provider tree with MEM_ENCRYPTION_CONTEXT inventory on
|
||||
# the root node provider.
|
||||
inventory = self._get_inventory()
|
||||
sev_inventory = {
|
||||
orc.MEM_ENCRYPTION_CONTEXT: {
|
||||
'total': 16,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0
|
||||
}
|
||||
}
|
||||
inventory.update(sev_inventory)
|
||||
self.pt.update_inventory(self.cn_rp['uuid'], inventory)
|
||||
# Now make up some fake allocations to pass back to the upt method
|
||||
# for the reshape
|
||||
allocations = {
|
||||
uuids.consumer1: {
|
||||
'allocations': {
|
||||
# This consumer has invalid MEM_ENCRYPTION_CONTEXT on
|
||||
# a non-root compute node provider.
|
||||
uuids.other_rp: {
|
||||
'resources': {
|
||||
orc.MEMORY_MB: 512,
|
||||
orc.MEM_ENCRYPTION_CONTEXT: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Initiate the reshape.
|
||||
ex = self.assertRaises(exception.ReshapeFailed,
|
||||
self.driver.update_provider_tree,
|
||||
self.pt, self.cn_rp['name'],
|
||||
allocations=allocations)
|
||||
self.assertIn('Unexpected MEM_ENCRYPTION_CONTEXT resource allocation '
|
||||
'on provider %s' % uuids.other_rp, str(ex))
|
||||
|
||||
@mock.patch('nova.objects.instance.Instance.get_by_uuid')
|
||||
@mock.patch('nova.objects.migration.MigrationList'
|
||||
'.get_in_progress_by_host_and_node')
|
||||
@@ -28798,15 +28994,6 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
trait = f'COMPUTE_GRAPHICS_MODEL_{model.upper()}'
|
||||
self.assertIn(trait, model_traits)
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_cpu_feature_traits',
|
||||
new=mock.Mock(return_value={}))
|
||||
def test_cpu_traits__sev_support(self):
|
||||
for support in (False, True):
|
||||
self.drvr._host._supports_amd_sev = support
|
||||
traits = self.drvr._get_cpu_traits()
|
||||
self.assertIn(ot.HW_CPU_X86_AMD_SEV, traits)
|
||||
self.assertEqual(support, traits[ot.HW_CPU_X86_AMD_SEV])
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_cpu_feature_traits',
|
||||
new=mock.Mock(return_value={}))
|
||||
def test_cpu_traits__hyperthreading_support(self):
|
||||
@@ -30981,25 +31168,25 @@ class TestLibvirtSEV(test.NoDBTestCase):
|
||||
|
||||
@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.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||
def test_get_memory_encryption_inventories_no_config(self):
|
||||
self.assertEqual({}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
def test_get_mem_encrypted_slots_config_zero(self):
|
||||
def test_get_memory_encryption_inventories_config_zero(self):
|
||||
self.flags(num_memory_encrypted_guests=0, group='libvirt')
|
||||
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||
self.assertEqual({}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
@mock.patch.object(libvirt_driver.LOG, 'warning')
|
||||
def test_get_mem_encrypted_slots_config_non_zero_unsupported(
|
||||
def test_get_memory_encryption_inventories_config_non_zero_unsupported(
|
||||
self, mock_log):
|
||||
self.flags(num_memory_encrypted_guests=16, group='libvirt')
|
||||
# Still zero without mocked SEV support
|
||||
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||
self.assertEqual({}, self.driver._get_memory_encryption_inventories())
|
||||
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.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||
def test_get_memory_encryption_inventories_unsupported(self):
|
||||
self.assertEqual({}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
@@ -31008,21 +31195,50 @@ class TestLibvirtSEVSupportedNoMaxGuests(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.assertEqual(db_const.MAX_INT,
|
||||
self.driver._get_memory_encrypted_slots())
|
||||
def test_get_memory_encryption_inventories_unlimited(self):
|
||||
self.assertEqual({
|
||||
'amd_sev': {
|
||||
'total': db_const.MAX_INT,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
}
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
@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):
|
||||
def test_get_memory_encryption_inventories_config_non_zero_supported(self):
|
||||
self.flags(num_memory_encrypted_guests=16, group='libvirt')
|
||||
self.assertEqual(16, self.driver._get_memory_encrypted_slots())
|
||||
self.assertEqual({
|
||||
'amd_sev': {
|
||||
'total': 16,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
}
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
|
||||
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
|
||||
def test_get_mem_encrypted_slots_config_zero_supported(self):
|
||||
def test_get_memory_encryption_inventories_config_zero_supported(self):
|
||||
self.flags(num_memory_encrypted_guests=0, group='libvirt')
|
||||
self.assertEqual(0, self.driver._get_memory_encrypted_slots())
|
||||
self.assertEqual({
|
||||
'amd_sev': {
|
||||
'total': 0,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
},
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
|
||||
|
||||
@mock.patch.object(vc, '_domain_capability_features',
|
||||
@@ -31032,16 +31248,36 @@ class TestLibvirtSEVSupportedMaxGuests(TestLibvirtSEV):
|
||||
@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_no_override(self, mock_log):
|
||||
self.assertEqual(100, self.driver._get_memory_encrypted_slots())
|
||||
def test_get_memory_encryption_inventories_no_override(self, mock_log):
|
||||
self.assertEqual({
|
||||
'amd_sev': {
|
||||
'total': 100,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
},
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
mock_log.assert_not_called()
|
||||
|
||||
@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_overlide_more(self, mock_log):
|
||||
def test_get_memory_encryption_inventories_override_more(self, mock_log):
|
||||
self.flags(num_memory_encrypted_guests=120, group='libvirt')
|
||||
self.assertEqual(100, self.driver._get_memory_encrypted_slots())
|
||||
self.assertEqual({
|
||||
'amd_sev': {
|
||||
'total': 100,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
}
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
mock_log.assert_called_with(
|
||||
'Host is configured with libvirt.num_memory_encrypted_guests '
|
||||
'set to %d, but supports only %d.', 120, 100)
|
||||
@@ -31049,9 +31285,19 @@ class TestLibvirtSEVSupportedMaxGuests(TestLibvirtSEV):
|
||||
@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_override_less(self, mock_log):
|
||||
def test_get_memory_encryption_inventories_override_less(self, mock_log):
|
||||
self.flags(num_memory_encrypted_guests=80, group='libvirt')
|
||||
self.assertEqual(80, self.driver._get_memory_encrypted_slots())
|
||||
self.assertEqual({
|
||||
'amd_sev': {
|
||||
'total': 80,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
}
|
||||
}, self.driver._get_memory_encryption_inventories())
|
||||
mock_log.assert_not_called()
|
||||
|
||||
|
||||
|
@@ -9437,7 +9437,6 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
memory_mb = int(self._host.get_memory_mb_total())
|
||||
vcpus = len(self._get_vcpu_available())
|
||||
pcpus = len(self._get_pcpu_available())
|
||||
memory_enc_slots = self._get_memory_encrypted_slots()
|
||||
|
||||
# NOTE(yikun): If the inv record does not exists, the allocation_ratio
|
||||
# will use the CONF.xxx_allocation_ratio value if xxx_allocation_ratio
|
||||
@@ -9482,16 +9481,6 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
'reserved': 0,
|
||||
}
|
||||
|
||||
if memory_enc_slots:
|
||||
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
|
||||
# storage is shared, and we should not report the DISK_GB inventory in
|
||||
# the compute node provider.
|
||||
@@ -9523,6 +9512,9 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
self._update_provider_tree_for_vpmems(
|
||||
provider_tree, nodename, result, resources)
|
||||
|
||||
self._update_provider_tree_for_memory_encryption(
|
||||
provider_tree, nodename, allocations=allocations)
|
||||
|
||||
provider_tree.update_inventory(nodename, result)
|
||||
provider_tree.update_resources(nodename, resources)
|
||||
|
||||
@@ -9564,7 +9556,189 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
metadata=vpmem)
|
||||
resources[rc].add(resource_obj)
|
||||
|
||||
def _get_memory_encrypted_slots(self):
|
||||
def _update_provider_tree_for_memory_encryption(self, provider_tree,
|
||||
nodename, allocations):
|
||||
"""Updates the provider tree for MEM_ENCRYPTION_CONTEXT inventory.
|
||||
|
||||
Before 2025.2, MEM_ENCRYPTION_CONTEXT inventory and allocations were on
|
||||
the root compute node provider in the tree. Starting in 2025.2,
|
||||
the MEM_ENCRYPTION_CONTEXT inventory is on a child provider in
|
||||
the tree. As a result, this method will "reshape" the tree if necessary
|
||||
on first start of this compute service in 2025.2.
|
||||
|
||||
:param provider_tree: The ProviderTree to update.
|
||||
:param nodename: The ComputeNode.hypervisor_hostname, also known as
|
||||
the name of the root node provider in the tree for this host.
|
||||
:param allocations: If not None, indicates a reshape was requested and
|
||||
should be performed.
|
||||
:raises: nova.exception.ReshapeNeeded if ``allocations`` is None and
|
||||
the method determines a reshape of the tree is needed, i.e.
|
||||
MEM_ENCRYPTION_CONTEXT inventory and allocations must be migrated
|
||||
from the root node provider to a child provider of
|
||||
MEM_ENCRYPTION_CONTEXT resources in the tree.
|
||||
:raises: nova.exception.ReshapeFailed if the requested tree reshape
|
||||
fails for whatever reason.
|
||||
"""
|
||||
inventories_dict = self._get_memory_encryption_inventories()
|
||||
if not inventories_dict:
|
||||
return
|
||||
|
||||
me_rps = self._ensure_memory_encryption_providers(
|
||||
inventories_dict, provider_tree, nodename)
|
||||
|
||||
if self._is_reshape_needed_memory_encryption_on_root(provider_tree,
|
||||
nodename):
|
||||
if allocations is None:
|
||||
LOG.info('Requesting provider tree reshape in order to move '
|
||||
'memory encryption context inventory from the root '
|
||||
'compute node provider %s to a child provider.',
|
||||
nodename)
|
||||
raise exception.ReshapeNeeded()
|
||||
root_node = provider_tree.data(nodename)
|
||||
self._reshape_memory_encryption_resources(allocations, root_node,
|
||||
me_rps)
|
||||
if provider_tree.has_traits(nodename, [ot.HW_CPU_X86_AMD_SEV]):
|
||||
provider_tree.remove_traits(nodename, ot.HW_CPU_X86_AMD_SEV)
|
||||
if orc.MEM_ENCRYPTION_CONTEXT in root_node.inventory:
|
||||
del root_node.inventory[orc.MEM_ENCRYPTION_CONTEXT]
|
||||
provider_tree.update_inventory(nodename, root_node.inventory)
|
||||
|
||||
@staticmethod
|
||||
def _is_reshape_needed_memory_encryption_on_root(provider_tree, nodename):
|
||||
"""Determine if root RP has MEM_ENCRYPTION_CONTEXT inventories.
|
||||
|
||||
Check to see if the root compute node provider in the tree for
|
||||
this host already has MEM_ENCRYPTION_CONTEXT inventory because if it
|
||||
does, we either need to signal for a reshape (if
|
||||
_update_provider_tree_for_memory_encryption () has no allocations) or
|
||||
move the allocations within the ProviderTree if passed.
|
||||
|
||||
:param provider_tree: The ProviderTree object for this host.
|
||||
:param nodename: The ComputeNode.hypervisor_hostname, also known as
|
||||
the name of the root node provider in the tree for this host.
|
||||
:returns: boolean, whether we have MEM_ENCRYPTION_CONTEXT root
|
||||
inventory.
|
||||
"""
|
||||
root_node = provider_tree.data(nodename)
|
||||
return orc.MEM_ENCRYPTION_CONTEXT in root_node.inventory
|
||||
|
||||
def _ensure_memory_encryption_providers(self, inventories_dict,
|
||||
provider_tree, nodename):
|
||||
"""Ensures MEM_ENCRYPTION_CONTEXT inventory providers exist in the tree
|
||||
for $nodename.
|
||||
|
||||
MEM_ENCRYPTION_CONTEXT providers are named $nodename_$model, e.g.
|
||||
``somehost.foo.bar.com_amd_sev``.
|
||||
|
||||
:param inventories_dict: Dictionary of inventories for
|
||||
MEM_ENCRYPTION_CONTEXT class
|
||||
directly provided by _get_memory_encryption_inventories() and which
|
||||
looks like:
|
||||
{'amd_sev':
|
||||
{'total': $TOTAL,
|
||||
'min_unit': 1,
|
||||
'max_unit': 1,
|
||||
'step_size': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV],
|
||||
}
|
||||
}
|
||||
:param provider_tree: The ProviderTree to update.
|
||||
:param nodename: The ComputeNode.hypervisor_hostname, also known as
|
||||
the name of the root node provider in the tree for this host.
|
||||
:returns: dict, keyed by memory encryption model, to ProviderData
|
||||
object representing that resource provider in the tree
|
||||
"""
|
||||
me_rps = {}
|
||||
for me_id, inventory in inventories_dict.items():
|
||||
me_rp_name = '%s_%s' % (nodename, me_id)
|
||||
if not inventory['total']:
|
||||
if provider_tree.exists(me_rp_name):
|
||||
provider_tree.remove(me_rp_name)
|
||||
break
|
||||
if not provider_tree.exists(me_rp_name):
|
||||
provider_tree.new_child(me_rp_name, nodename)
|
||||
me_rp = provider_tree.data(me_rp_name)
|
||||
me_rps[me_id] = me_rp
|
||||
me_traits = inventory.pop('traits', [])
|
||||
me_inventory = {orc.MEM_ENCRYPTION_CONTEXT: inventory}
|
||||
provider_tree.update_inventory(me_rp_name, me_inventory)
|
||||
provider_tree.add_traits(me_rp_name, *me_traits)
|
||||
return me_rps
|
||||
|
||||
def _reshape_memory_encryption_resources(
|
||||
self, allocations, root_node, me_rps):
|
||||
for consumer_uuid, alloc_data in allocations.items():
|
||||
allocs = alloc_data['allocations']
|
||||
for rp_uuid in list(allocs):
|
||||
resources = allocs[rp_uuid]['resources']
|
||||
if orc.MEM_ENCRYPTION_CONTEXT in resources:
|
||||
self._reshape_memory_encryption_allocations(
|
||||
rp_uuid, root_node, consumer_uuid, alloc_data,
|
||||
resources, me_rps)
|
||||
|
||||
def _reshape_memory_encryption_allocations(
|
||||
self, rp_uuid, root_node, consumer_uuid, alloc_data, resources,
|
||||
me_rps):
|
||||
"""Update existing MEM_ENCRYPTION_CONTEXT allocations by moving them
|
||||
from the root node provider to the child provider for AMD SEV
|
||||
|
||||
:param rp_uuid: UUID of the MEM_ENCRYPTION_CONTEXT resource provider
|
||||
with allocations from consumer_uuid (should be the root node
|
||||
provider before reshaping occurs)
|
||||
:param root_node: ProviderData object for the root compute node
|
||||
resource provider in the provider tree
|
||||
:param consumer_uuid: UUID of the consumer (instance) with
|
||||
MEM_ENCRYPTION_CONTEXT allocations against the resource provider
|
||||
represented by rp_uuid
|
||||
:param alloc_data: dict of allocation information for consumer_uuid
|
||||
:param resources: dict, keyed by resource class, of resources allocated
|
||||
to consumer_uuid from rp_uuid
|
||||
:param me_rps: dict, keyed by memory encryption model, to ProviderData
|
||||
object representing that resource provider in the tree
|
||||
:raises: ReshapeFailed if the reshape fails for whatever reason
|
||||
"""
|
||||
self._assert_is_root_provider(
|
||||
orc.MEM_ENCRYPTION_CONTEXT, rp_uuid, root_node, consumer_uuid,
|
||||
alloc_data)
|
||||
|
||||
sev_rp = None
|
||||
for me_rp_name in me_rps:
|
||||
if ot.HW_CPU_X86_AMD_SEV in me_rps[me_rp_name].traits:
|
||||
sev_rp = me_rps[me_rp_name]
|
||||
break
|
||||
|
||||
if sev_rp is None:
|
||||
msg = (_('MEM_ENCRYPTION_CONTEXT resources in the root provider '
|
||||
'%(rp_uuid)s are allocated by %(consumer_uuid)s but '
|
||||
'the child resource provider for AMD SEV is not found.')
|
||||
% {'rp_uuid': rp_uuid, 'consumer_uuid': consumer_uuid})
|
||||
raise exception.ReshapeFailed(error=msg)
|
||||
|
||||
allocs = alloc_data['allocations']
|
||||
allocs[sev_rp.uuid] = {
|
||||
'resources': {
|
||||
orc.MEM_ENCRYPTION_CONTEXT: 1
|
||||
}
|
||||
}
|
||||
del resources[orc.MEM_ENCRYPTION_CONTEXT]
|
||||
|
||||
def _get_memory_encryption_inventories(self):
|
||||
"""Returns the inventories for MEM_ENCRYPTION_CONTEXT.
|
||||
|
||||
:returns: dict, keyed by memory encryption model, of dicts like:
|
||||
{'amd_sev':
|
||||
{'total': $TOTAL,
|
||||
'min_unit': 1,
|
||||
'max_unit': 1,
|
||||
'step_size': 1,
|
||||
'reserved': 0,
|
||||
'allocation_ratio': 1.0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
}
|
||||
}
|
||||
"""
|
||||
conf_slots = CONF.libvirt.num_memory_encrypted_guests
|
||||
|
||||
if not self._host.supports_amd_sev:
|
||||
@@ -9572,7 +9746,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
LOG.warning("Host is configured with "
|
||||
"libvirt.num_memory_encrypted_guests set to "
|
||||
"%d, but is not SEV-capable.", conf_slots)
|
||||
return 0
|
||||
return {}
|
||||
|
||||
slots = db_const.MAX_INT
|
||||
|
||||
@@ -9587,8 +9761,18 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
"but supports only %d.", conf_slots, slots)
|
||||
slots = min(slots, conf_slots)
|
||||
|
||||
LOG.debug("Available memory encrypted slots: %d", slots)
|
||||
return slots
|
||||
LOG.debug("Available memory encrypted slots: AMD SEV=%d", slots)
|
||||
return {
|
||||
'amd_sev': {
|
||||
'total': slots,
|
||||
'step_size': 1,
|
||||
'max_unit': 1,
|
||||
'min_unit': 1,
|
||||
'allocation_ratio': 1.0,
|
||||
'reserved': 0,
|
||||
'traits': [ot.HW_CPU_X86_AMD_SEV]
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def static_traits(self) -> ty.Dict[str, bool]:
|
||||
@@ -9688,12 +9872,13 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
|
||||
@staticmethod
|
||||
def _assert_is_root_provider(
|
||||
rp_uuid, root_node, consumer_uuid, alloc_data):
|
||||
rc_name, rp_uuid, root_node, consumer_uuid, alloc_data):
|
||||
"""Asserts during a reshape that rp_uuid is for the root node provider.
|
||||
|
||||
When reshaping, inventory and allocations should be on the root node
|
||||
provider and then moved to child providers.
|
||||
|
||||
:param rc_name: Resource class name
|
||||
:param rp_uuid: UUID of the provider that holds inventory/allocations.
|
||||
:param root_node: ProviderData object representing the root node in a
|
||||
provider tree.
|
||||
@@ -9705,15 +9890,16 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
expected.
|
||||
"""
|
||||
if rp_uuid != root_node.uuid:
|
||||
# Something is wrong - VGPU inventory should
|
||||
# Something is wrong - the inventory should
|
||||
# only be on the root node provider if we are
|
||||
# reshaping the tree.
|
||||
msg = (_('Unexpected VGPU resource allocation '
|
||||
msg = (_('Unexpected %(rc_name)s resource allocation '
|
||||
'on provider %(rp_uuid)s for consumer '
|
||||
'%(consumer_uuid)s: %(alloc_data)s. '
|
||||
'Expected VGPU allocation to be on root '
|
||||
'Expected %(rc_name)s allocation to be on root '
|
||||
'compute node provider %(root_uuid)s.')
|
||||
% {'rp_uuid': rp_uuid,
|
||||
% {'rc_name': rc_name,
|
||||
'rp_uuid': rp_uuid,
|
||||
'consumer_uuid': consumer_uuid,
|
||||
'alloc_data': alloc_data,
|
||||
'root_uuid': root_node.uuid})
|
||||
@@ -9827,7 +10013,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
# We've found VGPU allocations on a provider. It should be the root
|
||||
# node provider.
|
||||
self._assert_is_root_provider(
|
||||
rp_uuid, root_node, consumer_uuid, alloc_data)
|
||||
orc.VGPU, rp_uuid, root_node, consumer_uuid, alloc_data)
|
||||
|
||||
# Find which physical GPU corresponds to this allocation.
|
||||
mdev_uuids = self._get_assigned_mdevs_for_reshape(
|
||||
@@ -13220,7 +13406,6 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
:return: A dict of trait names mapped to boolean values.
|
||||
"""
|
||||
traits = self._get_cpu_feature_traits()
|
||||
traits[ot.HW_CPU_X86_AMD_SEV] = self._host.supports_amd_sev
|
||||
traits[ot.HW_CPU_HYPERTHREADING] = self._host.has_hyperthreading
|
||||
traits.update(self._get_cpu_arch_traits())
|
||||
traits.update(self._get_cpu_emulation_arch_traits())
|
||||
|
Reference in New Issue
Block a user