fix(labsim): console-apply must handle both VyOS prompts, not just $

Two failures from one strict expect, both hit while building the sim ISPs.

A run that dies mid-config leaves the console parked in configuration mode. The
next run then waits for the operational `$ ` prompt against a perfectly healthy
VM and hangs until timeout, with nothing in the output to say why -- the box was
sitting at `vyos@isp-dhcp#` the whole time. Login now accepts `# ` as well and
discards the stale candidate rather than committing something nobody has seen.

The same mistake at the exit step: insisting on `$ ` after `save` hung, AND left
the console in config mode, which is what created the first failure for the
following run. Now accepts either prompt.

Known-bad, not fixed: the tool reports "committed and saved" when the set
commands have not applied. Verified against the clone -- prompt showed the
host-name change had landed while `grep -c dhcp-server` returned 0. The failure
detection only inspects c.before for a few strings and evidently misses the real
failure mode, so success is being reported without evidence. That needs fixing
before this tool is trusted for anything; it is currently only safe to use with
an independent check afterwards, which is how the gap was found.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
This commit is contained in:
Michal
2026-08-18 01:00:19 +01:00
parent ee070371a8
commit ff86a421f4

View File

@@ -38,23 +38,39 @@ def main() -> int:
# Log in. A freshly booted box may still be starting services, so allow a
# generous window and re-prod the console rather than failing on the first
# miss.
#
# `# ` matters as much as `$ `: a previous run that died mid-config leaves
# the console sitting in configuration mode, and waiting only for the
# operational prompt then hangs forever against a perfectly healthy VM.
in_config = False
for _ in range(40):
i = c.expect([r"login:", r"\$ ", r"# ", pexpect.TIMEOUT], timeout=15)
if i == 0:
c.sendline(args.user)
c.expect("Password:", timeout=30)
c.sendline(args.password)
c.expect(r"\$ ", timeout=60)
c.expect([r"\$ ", r"# "], timeout=60)
break
if i in (1, 2):
if i == 1:
break
if i == 2:
in_config = True
break
c.sendline("")
else:
print("never reached a prompt", file=sys.stderr)
return 1
c.sendline("configure")
c.expect(r"# ", timeout=60)
if in_config:
# Drop whatever the previous run left half-built rather than committing
# a candidate nobody has seen.
print("console was left in config mode; discarding stale candidate",
file=sys.stderr)
c.sendline("discard")
c.expect(r"# ", timeout=60)
else:
c.sendline("configure")
c.expect(r"# ", timeout=60)
for cmd in cmds:
c.sendline(cmd)
@@ -69,10 +85,14 @@ def main() -> int:
commit_out = c.before or ""
c.sendline("save")
c.expect(r"# ", timeout=120)
# Accept either prompt on the way out. Insisting on `$ ` here hangs against
# a healthy box -- and worse, leaves the console parked in config mode, so
# the NEXT run finds a `# ` it was not expecting either. One strict expect
# turned into two failures.
c.sendline("exit")
c.expect(r"\$ ", timeout=60)
c.expect([r"\$ ", r"# ", pexpect.TIMEOUT], timeout=60)
c.sendline("exit")
c.close()
c.close(force=True)
bad = [l for l in commit_out.splitlines()
if "failed" in l.lower() or "error" in l.lower()]