Skip to content

Commit 1867174

Browse files
c-poclaude
andcommitted
Testsuite: T3871: extend testifname with diagnostics and mac-order regression
Add diagnostic CLI/log output after each reboot in --ifnametest so a failure shows hw-id bindings, interface state and kernel/naming log lines directly on the console instead of only the final MAC-mapping exception. Split the single reboot into independent scenarios, since a pending node (hw-id cleared, node kept) only auto-resolves when it's the sole candidate of its type in that boot: - full interface delete, verifying it backfills its own gap - hw-id-only delete, verifying the pending node reclaims its own hardware - a deterministic MAC-order-mismatch case (swapped eth1/eth2 hw-id, simulating an already-provisioned box) combined with a delete + hw-id-clear in the same boot, verifying the settings-bearing node is never silently bound to the other freed NIC's hardware Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent aeb3483 commit 1867174

1 file changed

Lines changed: 181 additions & 3 deletions

File tree

scripts/check-qemu-install

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,64 @@ def verify_eth_mac_mapping(c, log):
722722
raise Exception(f'Interface {ifname} has MAC {macs[ifname]}, expected {expected_mac} - naming race?')
723723
log.info('eth0..eth7 MAC mapping verified')
724724

725+
def verify_swapped_hwid_assignment(c, log, mac1, mac2):
726+
""" eth1/eth2 were just explicitly reassigned to the opposite of their
727+
default MAC order (eth1 -> the numerically higher MAC, eth2 -> the
728+
lower one) - an ordinary, unambiguous rightful-owner rename that
729+
must take effect cleanly. This is the precondition for the next
730+
check: an existing box whose interface names do not follow
731+
ascending PCIe/MAC order, exactly like a real, already-provisioned
732+
system (its hw-id came from historical probe-order rescan, not
733+
from this sort). """
734+
log.info('Verify the swapped eth1/eth2 hw-id assignment took effect')
735+
c.sendline('ip -json link show | jq -r \'.[] | select(.ifname|test("^eth[0-9]+$")) | "\(.ifname) \(.address)"\'')
736+
c.expect(op_mode_prompt)
737+
lines = [l.strip() for l in c.before.decode(errors='replace').splitlines() if l.strip()]
738+
739+
macs = {}
740+
for line in lines:
741+
parts = line.split()
742+
if len(parts) == 2 and re.fullmatch(r'eth\d+', parts[0]):
743+
macs[parts[0]] = parts[1].lower()
744+
745+
if macs.get('eth1') != mac2:
746+
raise Exception(f'Interface eth1 has MAC {macs.get("eth1")}, expected {mac2} '
747+
'- swapped hw-id assignment did not take effect')
748+
if macs.get('eth2') != mac1:
749+
raise Exception(f'Interface eth2 has MAC {macs.get("eth2")}, expected {mac1} '
750+
'- swapped hw-id assignment did not take effect')
751+
log.info('Swapped hw-id assignment confirmed - eth1/eth2 no longer follow ascending MAC order')
752+
753+
def verify_pending_node_never_gets_wrong_hardware(c, log, ifname, wrong_mac):
754+
""" Regression reported against an earlier PCIe/MAC-sorted replacement
755+
fill: with an unrelated interface fully removed in the same boot
756+
that `ifname`'s hw-id alone was cleared, there is no way to tell
757+
which of the freed candidates is genuinely `ifname`'s own
758+
hardware once its hw-id is gone - so the fix leaves `ifname`
759+
pending (safely unresolved) rather than guessing. `ifname` must
760+
never end up bound to `wrong_mac` - the OTHER freed interface's
761+
hardware - which would silently apply a setting configured on
762+
`ifname` (e.g. address) to a different physical NIC. `ifname`
763+
simply not existing (still pending) is the expected, safe
764+
outcome here, not a failure. """
765+
log.info(f'Verify {ifname} was never bound to the wrong physical NIC')
766+
c.sendline('ip -json link show | jq -r \'.[] | select(.ifname|test("^eth[0-9]+$")) | "\(.ifname) \(.address)"\'')
767+
c.expect(op_mode_prompt)
768+
lines = [l.strip() for l in c.before.decode(errors='replace').splitlines() if l.strip()]
769+
770+
macs = {}
771+
for line in lines:
772+
parts = line.split()
773+
if len(parts) == 2 and re.fullmatch(r'eth\d+', parts[0]):
774+
macs[parts[0]] = parts[1].lower()
775+
776+
if macs.get(ifname) == wrong_mac:
777+
raise Exception(f'Interface {ifname} has MAC {wrong_mac} - bound to the '
778+
"OTHER freed interface's hardware instead of its own "
779+
f'(a setting configured on {ifname} is now applied to '
780+
'the wrong wire)')
781+
log.info(f'{ifname} was not bound to the wrong physical NIC')
782+
725783
def _image_update_cli_sequence(c, log, new_image_name, server_bind_host='127.0.0.1', use_vrf=False):
726784
"""One add-system-image/delete cycle for nested ISO over HTTP (optional Linux VRF + VyOS vrf arg)."""
727785
url = f'http://{server_bind_host}:{NESTED_HTTP_SERV_PORT}/{NESTED_INNER_ISO_NAME}'
@@ -1317,15 +1375,56 @@ try:
13171375
elif args.ifnametest:
13181376
# A missing/deleted hw-id binding, or a fully deleted interface
13191377
# config, must not change the eth0..eth7 <-> MAC mapping after
1320-
# the next reboot (regression check for the boot-time naming race).
1378+
# the next reboot (regression check for the boot-time naming
1379+
# race). Deliberately run as two INDEPENDENT reboots rather than
1380+
# one combined one: a pending node (hw-id cleared, node kept)
1381+
# only ever recovers its own hardware automatically when it's
1382+
# the sole candidate of its type this boot - if a different,
1383+
# unrelated interface's config were ALSO fully removed in the
1384+
# same boot, the two freed NICs become genuinely indistinguishable
1385+
# candidates and neither auto-resolves (see
1386+
# verify_pending_node_never_gets_wrong_hardware() below for why
1387+
# guessing there is unsafe). Testing each mechanism in its own
1388+
# boot is what each can actually guarantee.
13211389
log.info('Running interface naming/hw-id persistence tests')
13221390
del_idx, hwid_idx = random.sample(range(8), 2)
1323-
log.info(f'Deleting eth{del_idx} entirely, removing hw-id only on eth{hwid_idx}')
13241391

1392+
log.info(f'Deleting eth{del_idx} entirely')
13251393
c.sendline('configure')
13261394
c.expect(cfg_mode_prompt)
13271395
c.sendline(f'delete interfaces ethernet eth{del_idx}')
13281396
c.expect(cfg_mode_prompt)
1397+
c.sendline('commit')
1398+
c.expect(cfg_mode_prompt)
1399+
c.sendline('save')
1400+
c.expect(cfg_mode_prompt)
1401+
c.sendline('exit')
1402+
c.expect(op_mode_prompt)
1403+
1404+
log.info('Rebooting to verify the fully deleted interface backfills its own gap')
1405+
c.sendline('reboot now')
1406+
waitForLogin(c, log)
1407+
loginVM(c, log)
1408+
1409+
log.info('Collecting interface naming diagnostics')
1410+
c.sendline('show configuration commands | match "hw-id"')
1411+
c.expect(op_mode_prompt)
1412+
c.sendline('show interfaces ethernet')
1413+
c.expect(op_mode_prompt)
1414+
c.sendline('ip link show')
1415+
c.expect(op_mode_prompt)
1416+
c.sendline('show log | match "hw-id"')
1417+
c.expect(op_mode_prompt)
1418+
c.sendline('cat /run/vyos-net-name-resolve.json 2>/dev/null || true')
1419+
c.expect(op_mode_prompt)
1420+
c.sendline('show log kernel | match "eth"')
1421+
c.expect(op_mode_prompt)
1422+
1423+
verify_eth_mac_mapping(c, log)
1424+
1425+
log.info(f"Removing hw-id only on eth{hwid_idx}, keeping its node")
1426+
c.sendline('configure')
1427+
c.expect(cfg_mode_prompt)
13291428
c.sendline(f'delete interfaces ethernet eth{hwid_idx} hw-id')
13301429
c.expect(cfg_mode_prompt)
13311430
c.sendline('commit')
@@ -1335,7 +1434,7 @@ try:
13351434
c.sendline('exit')
13361435
c.expect(op_mode_prompt)
13371436

1338-
log.info('Rebooting to verify interface naming survives across reboot')
1437+
log.info('Rebooting to verify the pending node reclaims its own hardware')
13391438
c.sendline('reboot now')
13401439
waitForLogin(c, log)
13411440
loginVM(c, log)
@@ -1356,6 +1455,85 @@ try:
13561455

13571456
verify_eth_mac_mapping(c, log)
13581457

1458+
# Second, separate regression: a settings-bearing node must
1459+
# reclaim its OWN hardware, not whatever an ascending PCIe/MAC
1460+
# sort hands it, when an unrelated interface is fully removed in
1461+
# the same boot. The harness's own MACs are sequential
1462+
# (macbase:00..07), so a random del_idx/hwid_idx pair here would
1463+
# never expose this - a fresh install's initial bootstrap already
1464+
# sorts names and MACs together. Deterministically invert eth1's
1465+
# and eth2's hw-id first, so their names no longer follow
1466+
# ascending MAC order - exactly like any already-provisioned box,
1467+
# whose hw-id came from historical probe-order rescan rather than
1468+
# this sort.
1469+
log.info('Simulating an existing box whose interface names do not '
1470+
'follow PCIe/MAC order, then replacing one NIC while a '
1471+
'different, unrelated interface is fully removed')
1472+
mac1 = f'{macbase}:01'.lower()
1473+
mac2 = f'{macbase}:02'.lower()
1474+
1475+
c.sendline('configure')
1476+
c.expect(cfg_mode_prompt)
1477+
c.sendline('delete interfaces ethernet eth1 hw-id')
1478+
c.expect(cfg_mode_prompt)
1479+
c.sendline('delete interfaces ethernet eth2 hw-id')
1480+
c.expect(cfg_mode_prompt)
1481+
c.sendline(f"set interfaces ethernet eth1 hw-id '{mac2}'")
1482+
c.expect(cfg_mode_prompt)
1483+
c.sendline(f"set interfaces ethernet eth2 hw-id '{mac1}'")
1484+
c.expect(cfg_mode_prompt)
1485+
c.sendline("set interfaces ethernet eth2 address '10.99.2.1/24'")
1486+
c.expect(cfg_mode_prompt)
1487+
c.sendline('commit')
1488+
c.expect(cfg_mode_prompt)
1489+
c.sendline('save')
1490+
c.expect(cfg_mode_prompt)
1491+
c.sendline('exit')
1492+
c.expect(op_mode_prompt)
1493+
1494+
log.info('Rebooting to establish the swapped hw-id assignment as '
1495+
"this box's existing state")
1496+
c.sendline('reboot now')
1497+
waitForLogin(c, log)
1498+
loginVM(c, log)
1499+
1500+
verify_swapped_hwid_assignment(c, log, mac1, mac2)
1501+
1502+
log.info('Fully removing eth1 while only clearing eth2\'s hw-id, '
1503+
'keeping its address - the exact shape reported to '
1504+
'silently bind a configured node to the wrong physical NIC')
1505+
c.sendline('configure')
1506+
c.expect(cfg_mode_prompt)
1507+
c.sendline('delete interfaces ethernet eth1')
1508+
c.expect(cfg_mode_prompt)
1509+
c.sendline('delete interfaces ethernet eth2 hw-id')
1510+
c.expect(cfg_mode_prompt)
1511+
c.sendline('commit')
1512+
c.expect(cfg_mode_prompt)
1513+
c.sendline('save')
1514+
c.expect(cfg_mode_prompt)
1515+
c.sendline('exit')
1516+
c.expect(op_mode_prompt)
1517+
1518+
log.info('Rebooting to check eth2 is never bound to the wrong physical NIC')
1519+
c.sendline('reboot now')
1520+
waitForLogin(c, log)
1521+
loginVM(c, log)
1522+
1523+
log.info('Collecting mac-order-mismatch diagnostics')
1524+
c.sendline('show configuration commands | match "hw-id"')
1525+
c.expect(op_mode_prompt)
1526+
c.sendline('show interfaces ethernet')
1527+
c.expect(op_mode_prompt)
1528+
c.sendline('ip link show')
1529+
c.expect(op_mode_prompt)
1530+
c.sendline('show log | match "hw-id"')
1531+
c.expect(op_mode_prompt)
1532+
c.sendline('cat /run/vyos-net-name-resolve.json 2>/dev/null || true')
1533+
c.expect(op_mode_prompt)
1534+
1535+
verify_pending_node_never_gets_wrong_hardware(c, log, 'eth2', mac2)
1536+
13591537
elif args.raid:
13601538
# Verify RAID subsystem - by deleting a disk and re-create the array
13611539
# from scratch

0 commit comments

Comments
 (0)