Skip to content

Commit f5a6462

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 aee889f commit f5a6462

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
@@ -716,6 +716,64 @@ def verify_eth_mac_mapping(c, log):
716716
raise Exception(f'Interface {ifname} has MAC {macs[ifname]}, expected {expected_mac} - naming race?')
717717
log.info('eth0..eth7 MAC mapping verified')
718718

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

1386+
log.info(f'Deleting eth{del_idx} entirely')
13191387
c.sendline('configure')
13201388
c.expect(cfg_mode_prompt)
13211389
c.sendline(f'delete interfaces ethernet eth{del_idx}')
13221390
c.expect(cfg_mode_prompt)
1391+
c.sendline('commit')
1392+
c.expect(cfg_mode_prompt)
1393+
c.sendline('save')
1394+
c.expect(cfg_mode_prompt)
1395+
c.sendline('exit')
1396+
c.expect(op_mode_prompt)
1397+
1398+
log.info('Rebooting to verify the fully deleted interface backfills its own gap')
1399+
c.sendline('reboot now')
1400+
waitForLogin(c, log)
1401+
loginVM(c, log)
1402+
1403+
log.info('Collecting interface naming diagnostics')
1404+
c.sendline('show configuration commands | match "hw-id"')
1405+
c.expect(op_mode_prompt)
1406+
c.sendline('show interfaces ethernet')
1407+
c.expect(op_mode_prompt)
1408+
c.sendline('ip link show')
1409+
c.expect(op_mode_prompt)
1410+
c.sendline('show log | match "hw-id"')
1411+
c.expect(op_mode_prompt)
1412+
c.sendline('cat /run/vyos-net-name-resolve.json 2>/dev/null || true')
1413+
c.expect(op_mode_prompt)
1414+
c.sendline('show log kernel | match "eth"')
1415+
c.expect(op_mode_prompt)
1416+
1417+
verify_eth_mac_mapping(c, log)
1418+
1419+
log.info(f"Removing hw-id only on eth{hwid_idx}, keeping its node")
1420+
c.sendline('configure')
1421+
c.expect(cfg_mode_prompt)
13231422
c.sendline(f'delete interfaces ethernet eth{hwid_idx} hw-id')
13241423
c.expect(cfg_mode_prompt)
13251424
c.sendline('commit')
@@ -1329,7 +1428,7 @@ try:
13291428
c.sendline('exit')
13301429
c.expect(op_mode_prompt)
13311430

1332-
log.info('Rebooting to verify interface naming survives across reboot')
1431+
log.info('Rebooting to verify the pending node reclaims its own hardware')
13331432
c.sendline('reboot now')
13341433
waitForLogin(c, log)
13351434
loginVM(c, log)
@@ -1350,6 +1449,85 @@ try:
13501449

13511450
verify_eth_mac_mapping(c, log)
13521451

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

0 commit comments

Comments
 (0)