Skip to content

Commit 70deb14

Browse files
authored
Merge pull request #1268 from c-po/boot-ifname-race-2
Testsuite: T3871: extend testifname with diagnostics and mac-order regression
2 parents 6e19364 + 1867174 commit 70deb14

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

750+
def verify_swapped_hwid_assignment(c, log, mac1, mac2):
751+
""" eth1/eth2 were just explicitly reassigned to the opposite of their
752+
default MAC order (eth1 -> the numerically higher MAC, eth2 -> the
753+
lower one) - an ordinary, unambiguous rightful-owner rename that
754+
must take effect cleanly. This is the precondition for the next
755+
check: an existing box whose interface names do not follow
756+
ascending PCIe/MAC order, exactly like a real, already-provisioned
757+
system (its hw-id came from historical probe-order rescan, not
758+
from this sort). """
759+
log.info('Verify the swapped eth1/eth2 hw-id assignment took effect')
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('eth1') != mac2:
771+
raise Exception(f'Interface eth1 has MAC {macs.get("eth1")}, expected {mac2} '
772+
'- swapped hw-id assignment did not take effect')
773+
if macs.get('eth2') != mac1:
774+
raise Exception(f'Interface eth2 has MAC {macs.get("eth2")}, expected {mac1} '
775+
'- swapped hw-id assignment did not take effect')
776+
log.info('Swapped hw-id assignment confirmed - eth1/eth2 no longer follow ascending MAC order')
777+
778+
def verify_pending_node_never_gets_wrong_hardware(c, log, ifname, wrong_mac):
779+
""" Regression reported against an earlier PCIe/MAC-sorted replacement
780+
fill: with an unrelated interface fully removed in the same boot
781+
that `ifname`'s hw-id alone was cleared, there is no way to tell
782+
which of the freed candidates is genuinely `ifname`'s own
783+
hardware once its hw-id is gone - so the fix leaves `ifname`
784+
pending (safely unresolved) rather than guessing. `ifname` must
785+
never end up bound to `wrong_mac` - the OTHER freed interface's
786+
hardware - which would silently apply a setting configured on
787+
`ifname` (e.g. address) to a different physical NIC. `ifname`
788+
simply not existing (still pending) is the expected, safe
789+
outcome here, not a failure. """
790+
log.info(f'Verify {ifname} was never bound to the wrong physical NIC')
791+
c.sendline('ip -json link show | jq -r \'.[] | select(.ifname|test("^eth[0-9]+$")) | "\(.ifname) \(.address)"\'')
792+
c.expect(op_mode_prompt)
793+
lines = [l.strip() for l in c.before.decode(errors='replace').splitlines() if l.strip()]
794+
795+
macs = {}
796+
for line in lines:
797+
parts = line.split()
798+
if len(parts) == 2 and re.fullmatch(r'eth\d+', parts[0]):
799+
macs[parts[0]] = parts[1].lower()
800+
801+
if macs.get(ifname) == wrong_mac:
802+
raise Exception(f'Interface {ifname} has MAC {wrong_mac} - bound to the '
803+
"OTHER freed interface's hardware instead of its own "
804+
f'(a setting configured on {ifname} is now applied to '
805+
'the wrong wire)')
806+
log.info(f'{ifname} was not bound to the wrong physical NIC')
807+
750808
def _image_update_cli_sequence(c, log, new_image_name, server_bind_host='127.0.0.1', use_vrf=False):
751809
"""One add-system-image/delete cycle for nested ISO over HTTP (optional Linux VRF + VyOS vrf arg)."""
752810
url = f'http://{server_bind_host}:{NESTED_HTTP_SERV_PORT}/{NESTED_INNER_ISO_NAME}'
@@ -1342,15 +1400,56 @@ try:
13421400
elif args.ifnametest:
13431401
# A missing/deleted hw-id binding, or a fully deleted interface
13441402
# config, must not change the eth0..eth7 <-> MAC mapping after
1345-
# the next reboot (regression check for the boot-time naming race).
1403+
# the next reboot (regression check for the boot-time naming
1404+
# race). Deliberately run as two INDEPENDENT reboots rather than
1405+
# one combined one: a pending node (hw-id cleared, node kept)
1406+
# only ever recovers its own hardware automatically when it's
1407+
# the sole candidate of its type this boot - if a different,
1408+
# unrelated interface's config were ALSO fully removed in the
1409+
# same boot, the two freed NICs become genuinely indistinguishable
1410+
# candidates and neither auto-resolves (see
1411+
# verify_pending_node_never_gets_wrong_hardware() below for why
1412+
# guessing there is unsafe). Testing each mechanism in its own
1413+
# boot is what each can actually guarantee.
13461414
log.info('Running interface naming/hw-id persistence tests')
13471415
del_idx, hwid_idx = random.sample(range(8), 2)
1348-
log.info(f'Deleting eth{del_idx} entirely, removing hw-id only on eth{hwid_idx}')
13491416

1417+
log.info(f'Deleting eth{del_idx} entirely')
13501418
c.sendline('configure')
13511419
c.expect(cfg_mode_prompt)
13521420
c.sendline(f'delete interfaces ethernet eth{del_idx}')
13531421
c.expect(cfg_mode_prompt)
1422+
c.sendline('commit')
1423+
c.expect(cfg_mode_prompt)
1424+
c.sendline('save')
1425+
c.expect(cfg_mode_prompt)
1426+
c.sendline('exit')
1427+
c.expect(op_mode_prompt)
1428+
1429+
log.info('Rebooting to verify the fully deleted interface backfills its own gap')
1430+
c.sendline('reboot now')
1431+
waitForLogin(c, log)
1432+
loginVM(c, log)
1433+
1434+
log.info('Collecting interface naming diagnostics')
1435+
c.sendline('show configuration commands | match "hw-id"')
1436+
c.expect(op_mode_prompt)
1437+
c.sendline('show interfaces ethernet')
1438+
c.expect(op_mode_prompt)
1439+
c.sendline('ip link show')
1440+
c.expect(op_mode_prompt)
1441+
c.sendline('show log | match "hw-id"')
1442+
c.expect(op_mode_prompt)
1443+
c.sendline('cat /run/vyos-net-name-resolve.json 2>/dev/null || true')
1444+
c.expect(op_mode_prompt)
1445+
c.sendline('show log kernel | match "eth"')
1446+
c.expect(op_mode_prompt)
1447+
1448+
verify_eth_mac_mapping(c, log)
1449+
1450+
log.info(f"Removing hw-id only on eth{hwid_idx}, keeping its node")
1451+
c.sendline('configure')
1452+
c.expect(cfg_mode_prompt)
13541453
c.sendline(f'delete interfaces ethernet eth{hwid_idx} hw-id')
13551454
c.expect(cfg_mode_prompt)
13561455
c.sendline('commit')
@@ -1360,7 +1459,7 @@ try:
13601459
c.sendline('exit')
13611460
c.expect(op_mode_prompt)
13621461

1363-
log.info('Rebooting to verify interface naming survives across reboot')
1462+
log.info('Rebooting to verify the pending node reclaims its own hardware')
13641463
c.sendline('reboot now')
13651464
waitForLogin(c, log)
13661465
loginVM(c, log)
@@ -1381,6 +1480,85 @@ try:
13811480

13821481
verify_eth_mac_mapping(c, log)
13831482

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

0 commit comments

Comments
 (0)