Skip to content

Commit 7304c89

Browse files
committed
[VIRT] Add PCI topology stability tests
Verify that PCI device addresses remain unchanged across VM restart, migration, snapshot/restore, and CNV upgrade. - Add get_pci_fingerprint() to tests/virt/utils.py (md5 of sorted lspci) - Add tests/virt/node/general/test_pci_topology_stability.py with restart, migration, and snapshot/restore tests - Add PCI topology check to upgrade tests (before/after fingerprint comparison on all upgrade VMs) Signed-off-by: Samuel Albershtein <salbersh@redhat.com> Assisted-by: Claude <noreply@anthropic.com>
1 parent 1e4c510 commit 7304c89

4 files changed

Lines changed: 225 additions & 3 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
PCI Topology Stability Tests
3+
4+
STP: https://github.com/RedHatQE/openshift-virtualization-tests-design-docs/blob/main/stps/sig-virt/pci-topology-stability.md
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import logging
10+
from typing import TYPE_CHECKING
11+
12+
import pytest
13+
from ocp_resources.template import Template
14+
from ocp_resources.virtual_machine_restore import VirtualMachineRestore
15+
from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
16+
17+
from tests.os_params import RHEL_LATEST, RHEL_LATEST_LABELS
18+
from tests.virt.utils import get_pci_fingerprint
19+
from utilities.virt import (
20+
VirtualMachineForTestsFromTemplate,
21+
migrate_vm_and_verify,
22+
restart_vm_wait_for_running_vm,
23+
running_vm,
24+
)
25+
26+
if TYPE_CHECKING:
27+
from utilities.virt import VirtualMachineForTests
28+
29+
LOGGER = logging.getLogger(__name__)
30+
31+
pytestmark = [pytest.mark.rwx_default_storage, pytest.mark.data_collector_scope(scope="module")]
32+
33+
34+
@pytest.fixture(scope="class")
35+
def pci_topology_vm(
36+
request,
37+
namespace,
38+
unprivileged_client,
39+
golden_image_data_volume_template_for_test_scope_class,
40+
modern_cpu_for_migration,
41+
):
42+
with VirtualMachineForTestsFromTemplate(
43+
name=request.param["vm_name"],
44+
labels=Template.generate_template_labels(**request.param["template_labels"]),
45+
namespace=namespace.name,
46+
client=unprivileged_client,
47+
data_volume_template=golden_image_data_volume_template_for_test_scope_class,
48+
cpu_model=modern_cpu_for_migration,
49+
) as vm:
50+
running_vm(vm=vm)
51+
yield vm
52+
53+
54+
@pytest.fixture()
55+
def initial_pci_fingerprint(pci_topology_vm):
56+
return get_pci_fingerprint(vm=pci_topology_vm)
57+
58+
59+
@pytest.fixture()
60+
def restarted_pci_topology_vm(pci_topology_vm, initial_pci_fingerprint):
61+
restart_vm_wait_for_running_vm(vm=pci_topology_vm)
62+
return pci_topology_vm
63+
64+
65+
@pytest.fixture()
66+
def migrated_pci_topology_vm(admin_client, pci_topology_vm, initial_pci_fingerprint):
67+
migrate_vm_and_verify(vm=pci_topology_vm, client=admin_client, check_ssh_connectivity=True)
68+
return pci_topology_vm
69+
70+
71+
@pytest.fixture()
72+
def snapshot_restored_pci_topology_vm(admin_client, pci_topology_vm, initial_pci_fingerprint):
73+
with VirtualMachineSnapshot(
74+
name=f"{pci_topology_vm.name}-snapshot",
75+
namespace=pci_topology_vm.namespace,
76+
vm_name=pci_topology_vm.name,
77+
) as snapshot:
78+
snapshot.wait_snapshot_done()
79+
pci_topology_vm.stop(wait=True)
80+
LOGGER.info(f"Restoring VM {pci_topology_vm.name} from snapshot {snapshot.name}")
81+
with VirtualMachineRestore(
82+
client=admin_client,
83+
name=f"{pci_topology_vm.name}-restore",
84+
namespace=pci_topology_vm.namespace,
85+
vm_name=pci_topology_vm.name,
86+
snapshot_name=snapshot.name,
87+
) as restore:
88+
restore.wait_restore_done()
89+
running_vm(vm=pci_topology_vm)
90+
return pci_topology_vm
91+
92+
93+
@pytest.mark.parametrize(
94+
"golden_image_data_source_for_test_scope_class, pci_topology_vm",
95+
[
96+
pytest.param(
97+
{"os_dict": RHEL_LATEST},
98+
{"template_labels": RHEL_LATEST_LABELS, "vm_name": "pci-topology-vm"},
99+
),
100+
],
101+
indirect=True,
102+
)
103+
class TestPCITopologyStability:
104+
"""
105+
Verify PCI device addresses remain stable across VM lifecycle operations.
106+
107+
Preconditions:
108+
- RHEL VM created from latest template with a modern CPU model for migration,
109+
started and running with SSH access
110+
- PCI fingerprint (MD5 of sorted guest PCI BDF addresses) captured before each operation
111+
"""
112+
113+
@pytest.mark.polarion("CNV-16326")
114+
def test_pci_address_stability_on_restart(
115+
self,
116+
initial_pci_fingerprint: str,
117+
restarted_pci_topology_vm: VirtualMachineForTests,
118+
):
119+
"""
120+
Steps:
121+
1. Restart the VM and wait until it is running with SSH access
122+
2. Capture PCI fingerprint from the guest
123+
124+
Expected:
125+
- Fingerprints match (PCI topology unchanged)
126+
"""
127+
fingerprint_after = get_pci_fingerprint(vm=restarted_pci_topology_vm)
128+
assert fingerprint_after == initial_pci_fingerprint, (
129+
f"PCI topology changed after restart: before={initial_pci_fingerprint}, after={fingerprint_after}"
130+
)
131+
132+
@pytest.mark.polarion("CNV-16327")
133+
def test_pci_address_stability_on_migration(
134+
self,
135+
initial_pci_fingerprint: str,
136+
migrated_pci_topology_vm: VirtualMachineForTests,
137+
):
138+
"""
139+
Steps:
140+
1. Live-migrate the VM and verify SSH connectivity
141+
2. Capture PCI fingerprint from the guest
142+
143+
Expected:
144+
- Fingerprints match (PCI topology unchanged)
145+
"""
146+
fingerprint_after = get_pci_fingerprint(vm=migrated_pci_topology_vm)
147+
assert fingerprint_after == initial_pci_fingerprint, (
148+
f"PCI topology changed after migration: before={initial_pci_fingerprint}, after={fingerprint_after}"
149+
)
150+
151+
@pytest.mark.polarion("CNV-16328")
152+
@pytest.mark.usefixtures("skip_if_no_storage_class_for_snapshot")
153+
def test_pci_address_stability_on_snapshot_restore(
154+
self,
155+
initial_pci_fingerprint: str,
156+
snapshot_restored_pci_topology_vm: VirtualMachineForTests,
157+
):
158+
"""
159+
Preconditions:
160+
- Storage class supports snapshots
161+
162+
Steps:
163+
1. Create a VM snapshot and wait until it completes
164+
2. Stop the VM
165+
3. Restore the VM from the snapshot
166+
4. Start the VM and wait until it is running with SSH access
167+
5. Capture PCI fingerprint from the guest
168+
169+
Expected:
170+
- Fingerprints match (PCI topology unchanged)
171+
"""
172+
fingerprint_after = get_pci_fingerprint(vm=snapshot_restored_pci_topology_vm)
173+
assert fingerprint_after == initial_pci_fingerprint, (
174+
f"PCI topology changed after snapshot restore: before={initial_pci_fingerprint}, after={fingerprint_after}"
175+
)

tests/virt/upgrade/conftest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
vm_from_template,
2020
wait_for_automatic_vm_migrations,
2121
)
22-
from tests.virt.utils import get_boot_time_for_multiple_vms
22+
from tests.virt.utils import get_boot_time_for_multiple_vms, get_pci_fingerprint
2323
from utilities.artifactory import get_test_artifact_server_url
2424
from utilities.constants import Images
2525
from utilities.constants.images import OS_FLAVOR_RHEL
@@ -321,6 +321,11 @@ def virt_migratable_vms_names(virt_migratable_vms):
321321
return vm_names
322322

323323

324+
@pytest.fixture(scope="session")
325+
def pci_fingerprints_before_upgrade(vms_for_upgrade):
326+
return {vm.name: get_pci_fingerprint(vm=vm) for vm in vms_for_upgrade}
327+
328+
324329
@pytest.fixture(scope="session")
325330
def linux_boot_time_before_upgrade(vms_for_upgrade):
326331
return get_boot_time_for_multiple_vms(vm_list=vms_for_upgrade)

tests/virt/upgrade/test_upgrade_virt.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
verify_run_strategy_vmi_status,
2222
verify_vms_ssh_connectivity,
2323
)
24-
from tests.virt.utils import assert_migration_post_copy_mode, verify_guest_boot_time
24+
from tests.virt.utils import assert_migration_post_copy_mode, get_pci_fingerprint, verify_guest_boot_time
2525
from utilities.constants.hco import DATA_SOURCE_NAME
2626
from utilities.constants.pytest import DEPENDENCY_SCOPE_SESSION
2727
from utilities.exceptions import ResourceValueError
@@ -78,7 +78,8 @@ class TestUpgradeVirt:
7878
@pytest.mark.polarion("CNV-2974")
7979
@pytest.mark.order("first")
8080
@pytest.mark.dependency(name=VMS_RUNNING_BEFORE_UPGRADE_TEST_NODE_ID)
81-
def test_is_vm_running_before_upgrade(self, vms_for_upgrade, linux_boot_time_before_upgrade):
81+
@pytest.mark.usefixtures("linux_boot_time_before_upgrade", "pci_fingerprints_before_upgrade")
82+
def test_is_vm_running_before_upgrade(self, vms_for_upgrade):
8283
for vm in vms_for_upgrade:
8384
assert vm.vmi.status == VirtualMachineInstance.Status.RUNNING
8485

@@ -298,6 +299,30 @@ def test_vms_boot_time_after_upgrade(
298299
migratable_vms = [vm for vm in vms_for_upgrade if vm.name in virt_migratable_vms_names]
299300
verify_guest_boot_time(vm_list=migratable_vms, initial_boot_time=linux_boot_time_before_upgrade)
300301

302+
@pytest.mark.ocp_upgrade
303+
@pytest.mark.sno
304+
@pytest.mark.polarion("CNV-16329")
305+
@pytest.mark.order(
306+
after=[IMAGE_UPDATE_AFTER_UPGRADE_NODE_ID, VIRT_VMS_RUNNING_AFTER_UPGRADE_TEST_NODE_ID],
307+
before=AFTER_UPGRADE_STORAGE_ORDERING,
308+
)
309+
@pytest.mark.dependency(
310+
depends=[
311+
IUO_UPGRADE_TEST_DEPENDENCY_NODE_ID,
312+
VIRT_VMS_RUNNING_AFTER_UPGRADE_TEST_NODE_ID,
313+
VMS_RUNNING_BEFORE_UPGRADE_TEST_NODE_ID,
314+
],
315+
scope=DEPENDENCY_SCOPE_SESSION,
316+
)
317+
def test_pci_topology_after_upgrade(self, vms_for_upgrade, pci_fingerprints_before_upgrade):
318+
"""STP: https://github.com/RedHatQE/openshift-virtualization-tests-design-docs/blob/main/stps/sig-virt/pci-topology-stability.md"""
319+
failed_vms = {}
320+
for vm in vms_for_upgrade:
321+
current_fingerprint = get_pci_fingerprint(vm=vm)
322+
if current_fingerprint != pci_fingerprints_before_upgrade[vm.name]:
323+
failed_vms[vm.name] = {"before": pci_fingerprints_before_upgrade[vm.name], "after": current_fingerprint}
324+
assert not failed_vms, f"PCI topology changed after upgrade: {failed_vms}"
325+
301326
@pytest.mark.ocp_upgrade
302327
@pytest.mark.sno
303328
@pytest.mark.polarion("CNV-3682")

tests/virt/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,23 @@ def get_non_terminated_pods(client, node):
420420
)
421421

422422

423+
def get_pci_fingerprint(vm: VirtualMachineForTests) -> str:
424+
"""Get PCI fingerprint (md5 hash of sorted BDF addresses) from guest VM.
425+
426+
Args:
427+
vm: Running VM with SSH access.
428+
429+
Returns:
430+
MD5 hash string of the sorted PCI BDF addresses.
431+
"""
432+
fingerprint = run_ssh_commands(
433+
host=vm.ssh_exec,
434+
commands=["bash", "-o", "pipefail", "-c", "lspci | awk '{print $1}' | sort | md5sum"],
435+
)[0].strip()
436+
LOGGER.info(f"PCI fingerprint for VM {vm.name}: {fingerprint}")
437+
return fingerprint
438+
439+
423440
def get_boot_time_for_multiple_vms(vm_list):
424441
return {vm.name: get_vm_boot_time(vm=vm) for vm in vm_list}
425442

0 commit comments

Comments
 (0)