Merge "Functional tests for one-time-use devices"

This commit is contained in:
Zuul
2025-04-23 16:01:07 +00:00
committed by Gerrit Code Review
2 changed files with 185 additions and 8 deletions
@@ -99,7 +99,7 @@ class PlacementPCIInventoryReportingTests(PlacementPCIReportingTests):
)
},
# PF_PROD_ID + slot 2 will match one PF but not their children
# VFs
# VFs and will be considered OTU
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PF_PROD_ID,
@@ -107,6 +107,7 @@ class PlacementPCIInventoryReportingTests(PlacementPCIReportingTests):
"traits": ",".join(
[os_traits.HW_NIC_SRIOV, "CUSTOM_PF", "pf-white"]
),
"one_time_use": "true",
},
# VF_PROD_ID + slot 3 will match two VFs but not their parent
# PF
@@ -147,6 +148,7 @@ class PlacementPCIInventoryReportingTests(PlacementPCIReportingTests):
],
"0000:81:02.0": [
"HW_NIC_SRIOV",
"HW_PCI_ONE_TIME_USE",
"CUSTOM_PF",
"CUSTOM_PF_WHITE",
],
@@ -1995,3 +1997,170 @@ class RCAndTraitBasedPCIAliasTests(PlacementPCIReportingTests):
self.assert_placement_pci_view(
"compute1", **compute1_expected_placement_view)
self.assert_no_pci_healing("compute1")
def test_one_time_use_workflow(self):
# The fake libvirt will emulate on the host:
# * two type-PCIs
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=1, num_pfs=0, num_vfs=0)
device_spec = self._to_list_of_json_str(
[
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
"one_time_use": "true",
},
]
)
self.flags(group='pci', device_spec=device_spec)
pci_alias = {
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
"name": "a-otu",
}
self.flags(
group="pci",
alias=self._to_list_of_json_str([pci_alias]),
)
extra_spec = {"pci_passthrough:alias": "a-otu:1"}
flavor_id = self._create_flavor(extra_spec=extra_spec)
self.start_compute(hostname="compute1", pci_info=pci_info)
rc = "CUSTOM_PCI_%s_%s" % (fakelibvirt.PCI_VEND_ID,
fakelibvirt.PCI_PROD_ID)
addr = "0000:81:00.0"
expected_placement_view = {
"inventories": {
addr: {
rc: {
"total": 1,
"reserved": 0,
},
},
},
"traits": {
addr: [
"HW_PCI_ONE_TIME_USE",
],
},
"usages": {
addr: {rc: 0},
},
"allocations": {},
}
# Initially we expect no usage, no reservation
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
self.assertPCIDeviceCounts('compute1', total=1, free=1)
# Create a server that uses our device
server = self._create_server(flavor_id=flavor_id, networks=[])
# Now we expect the usage to have gone up, and for the reserved
# count on our OTU device to have increased
expected_placement_view["usages"][addr][rc] = 1
expected_placement_view["inventories"][addr][rc]["reserved"] = 1
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
# Delete the server to release the allocation (but not reservation)
self._delete_server(server)
# We expect the usage to have gone back to zero, but the reserved
# count is unchanged
expected_placement_view["usages"][addr][rc] = 0
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
# Create another server that wants our device, but there are no
# cleaned devices available. It should fail to schedule because
# no host can satisfy the request.
server = self._create_server(flavor_id=flavor_id, networks=[],
expected_state="ERROR")
self.assertIn('No valid host', server['fault']['message'])
self._delete_server(server)
# Externally "clean" the device and mark it as unreserved
self._reserve_placement_resource('compute1_%s' % addr, rc, 0)
expected_placement_view["inventories"][addr][rc]['reserved'] = 0
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
# Create a server that uses our device, confirming that after an
# external clean it is allocatable again.
server = self._create_server(flavor_id=flavor_id, networks=[])
def test_otu_configured_late(self):
# The fake libvirt will emulate on the host:
# * two type-PCIs
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=1, num_pfs=0, num_vfs=0)
device_spec = [
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
},
]
self.flags(
group='pci', device_spec=self._to_list_of_json_str(device_spec))
pci_alias = {
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
"name": "a-otu",
}
self.flags(
group="pci",
alias=self._to_list_of_json_str([pci_alias]),
)
extra_spec = {"pci_passthrough:alias": "a-otu:1"}
flavor_id = self._create_flavor(extra_spec=extra_spec)
self.start_compute(hostname="compute1", pci_info=pci_info)
rc = "CUSTOM_PCI_%s_%s" % (fakelibvirt.PCI_VEND_ID,
fakelibvirt.PCI_PROD_ID)
addr = "0000:81:00.0"
expected_placement_view = {
"inventories": {
addr: {
rc: {
"total": 1,
"reserved": 0,
},
},
},
"traits": {
addr: [],
},
"usages": {
addr: {rc: 0},
},
"allocations": {},
}
# Initially we expect no usage, no reservation
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
self.assertPCIDeviceCounts("compute1", total=1, free=1)
# Create a server that uses our device
self._create_server(flavor_id=flavor_id, networks=[])
# Expect to see the usage go up, but reserved stays at zero
expected_placement_view["usages"][addr][rc] = 1
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
# Now configure the compute node to treat our device as OTU and restart
# the compute service (which will run the placement update)
device_spec[0]['one_time_use'] = 'true'
self.flags(
group="pci", device_spec=self._to_list_of_json_str(device_spec))
self.restart_compute_service(hostname="compute1")
# Now we expect to see the device go reserved and the OTU trait get
# added to it.
expected_placement_view["inventories"][addr][rc]["reserved"] = 1
expected_placement_view["traits"][addr].append("HW_PCI_ONE_TIME_USE")
self.assert_placement_pci_view(
"compute1", **expected_placement_view)
@@ -179,13 +179,21 @@ class _PCIServersTestBase(base.ServersTestBase):
f"Number of inventories on {real_rp_name} are not as "
f"expected. Expected {inv}, actual {rp_inv}"
)
for rc, total in inv.items():
self.assertEqual(
total,
rp_inv[rc]["total"])
self.assertEqual(
total,
rp_inv[rc]["max_unit"])
for rc, inv_assertions in inv.items():
if isinstance(inv_assertions, int):
# Simple case where we just specify total to assert
total = inv_assertions
self.assertEqual(
total,
rp_inv[rc]["total"])
self.assertEqual(
total,
rp_inv[rc]["max_unit"])
else:
# In this case, inv_assertions is a dict of attributes
# in the inventory
inv_to_assert = {k: rp_inv[rc][k] for k in inv_assertions}
self.assertEqual(inv_to_assert, inv_assertions)
rp_traits = self._get_provider_traits(rp['uuid'])
self.assertEqual(