Skip to content

Commit 90ca215

Browse files
authored
Merge pull request #1255 from microsoft/agents/issue-549-boot-console-exit-drain
openvmm: pin the boot console's exit drain
2 parents be78f17 + 7e7f76f commit 90ca215

8 files changed

Lines changed: 76 additions & 34 deletions

File tree

‎doc/ci.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,10 @@ pair instead.
414414
The `console-exit` scenario delays host console reads for two seconds after
415415
snapshot restore to exercise output backpressure. For each requested processor
416416
count it requires byte-exact delivery of a 64 KiB payload and the final marker,
417-
and preserves guest exit statuses 0 and 37. This checks both device and host-relay
418-
draining without adding sleeps to the measured benchmark workloads.
417+
which the guest writes to the boot console before it exits with status 0 and
418+
to the byte channel before it exits with status 37, and preserves both
419+
statuses. This checks the draining of both consoles' devices and host relays
420+
without adding sleeps to the measured benchmark workloads.
419421
The harness waits for the output reader's EOF notification even after the
420422
process exits, so delayed final output chunks cannot create a false failure.
421423

‎doc/design/3.3-consoles.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ The host attachment is one of:
2525
- A listener on a Unix socket, a named pipe, or loopback TCP with a fixed
2626
nonzero port, which retains pending guest output until a client connects.
2727
- A client connection.
28-
- The inherited terminal.
28+
- The inherited terminal, whose output a guest-requested exit or capture
29+
writes before the process ends; see
30+
[Final Output and Exit Status](5.6-shutdown-and-failure-semantics.md#561-final-output-and-exit-status).
2931
- An explicitly disconnected endpoint that discards guest output.
3032

3133
The snapshot records the attachment's stable ID, canonical endpoint identity,

‎doc/design/5.6-shutdown-and-failure-semantics.md‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ after teardown reaches a known state.
1111
## 5.6.1 Final Output and Exit Status
1212

1313
Guest-requested exit drains the byte channel and, when present, its host
14-
output relay before reporting completion, within one combined five-second
15-
deadline. Success preserves the guest's exit status; an endpoint, relay, or
16-
timeout failure becomes a process-exit error instead of silently discarding
17-
final output. This guarantee covers the byte channel and its relay, not every
18-
console endpoint. Under the management RPC, a zero guest status completes a
19-
pending wait and stops the server cleanly, while a nonzero status or a failed
20-
drain fails the wait and ends the server with an error. After teardown, the
21-
VMM can write the bounded
14+
output relay before reporting completion, and writes the output that a
15+
[boot console](3.3-consoles.md#331-boot-console) on the host's standard
16+
streams accepted, within one combined five-second deadline. Success preserves
17+
the guest's exit status; an endpoint, relay, or timeout failure becomes a
18+
process-exit error instead of silently discarding final output. This guarantee
19+
covers the byte channel, its relay, and the boot console on the standard
20+
streams, not every console endpoint. Under the management RPC, a zero guest
21+
status completes a pending wait and stops the server cleanly, while a nonzero
22+
status or a failed drain fails the wait and ends the server with an error.
23+
After teardown, the VMM can write the bounded
2224
[outcome report](1.4-trust-boundaries.md#145-host-facing-outputs).
2325

2426
As Table 5.9 shows, the guest reports its own fail-fast conditions through

‎doc/openvmm-upstream-roadmap.md‎

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
`b2676222b383bf50c7022f6a5983920421469a47`; see [Upstream status](#upstream-status).
88
- Mapped fork tip: `3cdf4fe3834cfb90b9ee685875493bc9bff1b7a4`, position 135. The graph, PR
99
summary, and commit mapping below cover positions 001-135 and the PR20 fixup at position 152.
10-
- NVX's pinned OpenVMM revision: `eda13d80a85e49c16ed0500a9d4ae762ca810304`, position 210, the
11-
head of the `feature/microvm/release/v4.0` branch of microsoft/openvmm, to which
12-
`feature/microvm/dev` also points. The v4.0 reorganization moved the 205 commits of
13-
`feature/microvm/release/v3.0` and NVX's previous pin, `35e76265f`, onto `c6d3da0ff`. It
14-
dropped the eight commits whose changes upstream had landed or superseded, retired the fallible
15-
device start that microsoft/nvx#1232 replaces with awaited device restoration, folded that
16-
restoration into the commits that it changes, and added 14 commits that give each ABI that the
17-
chain introduces a single version; see [The v4.0 reorganization](#the-v40-reorganization). The
18-
pin is 75 commits past the mapped tip on the same first-parent chain. Positions 136-210, except
19-
the PR20 fixup at position 152, are not yet assigned to PRs; see
20-
[Commits after the mapped tip](#commits-after-the-mapped-tip).
10+
- NVX's pinned OpenVMM revision: `5aa8b028503237757b95f5629ebdbb4d56a67d51`, position 211.
11+
Position 211, which microsoft/openvmm#4894 proposes for `feature/microvm/dev`, is one commit on
12+
position 210, `eda13d80a`, the head of the `feature/microvm/release/v4.0` branch of
13+
microsoft/openvmm, to which `feature/microvm/dev` also points. The v4.0 reorganization moved the
14+
205 commits of `feature/microvm/release/v3.0` and the NVX pin before it, `35e76265f`, onto
15+
`c6d3da0ff`. It dropped the eight commits whose changes upstream had landed or superseded,
16+
retired the fallible device start that microsoft/nvx#1232 replaces with awaited device
17+
restoration, folded that restoration into the commits that it changes, and added 14 commits that
18+
give each ABI that the chain introduces a single version; see
19+
[The v4.0 reorganization](#the-v40-reorganization). The pin is 76 commits past the mapped tip on
20+
the same first-parent chain. Positions 136-211, except the PR20 fixup at position 152, are not
21+
yet assigned to PRs; see [Commits after the mapped tip](#commits-after-the-mapped-tip).
2122
- Upstream main: `ca6b2b177da5e33ae1bcac72986b31d6b039811c` (2026-11-02), one commit past the
2223
fork base; see [Upstream main past the fork base](#upstream-main-past-the-fork-base).
2324

@@ -643,7 +644,8 @@ c6d3da0ff
643644
-> command lines [189] -> KVP test [190-191]
644645
-> sandbox block roles [192] -> virtio feature masks [193-194]
645646
-> IOAPIC interrupt lines [195-196] -> ABI restart [197-210]
646-
-> eda13d80a (NVX pin)
647+
-> boot console exit drain [211]
648+
-> 5aa8b0285 (NVX pin)
647649
```
648650

649651
For upstream review, PR01-PR07 and PR14 can be rebased independently onto current upstream main,
@@ -659,12 +661,12 @@ see [The v3.0 reorganization](#the-v30-reorganization).
659661

660662
## Commits after the mapped tip
661663

662-
The NVX pin extends the chain past position 135 without merges. Of the 75 later commits, position
664+
The NVX pin extends the chain past position 135 without merges. Of the 76 later commits, position
663665
152 is a PR20 fixup, and the others are not yet classified or assigned to PRs. Each of positions
664666
136-191 combines the v2.4 commits that the table lists, as v3.0 positions 145-200 did, which
665667
developed one concept; "(part)" marks a v2.4 fixup that split across commits. Positions 192-196
666-
followed the v3.0 reorganization, and positions 197-210 are the ABI restart of the v4.0
667-
reorganization, so they combine none.
668+
followed the v3.0 reorganization, positions 197-210 are the ABI restart of the v4.0
669+
reorganization, and position 211 followed it, so they combine none.
668670

669671
| Position | Commit | v3.0 position | v2.4 positions | Topic |
670672
| ---: | --- | ---: | --- | --- |
@@ -743,6 +745,7 @@ reorganization, so they combine none.
743745
| 208 | `bec51b3ac` | - | - | vmservice: give the microVM fields upstream-compatible numbers |
744746
| 209 | `31d684ae0` | - | - | microVM: drop the history from the layout and snapshot helpers |
745747
| 210 | `eda13d80a` | - | - | openvmm_helpers: number the snapshot's fields without gaps |
748+
| 211 | `5aa8b0285` | - | - | microVM: drain the boot console's terminal output at guest exit (microsoft/openvmm#4894) |
746749

747750
The remarks that say whether commits apply to upstream main record what the v3.0 reorganization
748751
found on its base, `b2676222b`, at the positions that the commits have now.
@@ -769,7 +772,9 @@ inputs from the loader of PR15's position 066. Position 196 moves every virtio s
769772
IRQ that PR16's position 071 gives it, and the NIC from the backend-specific IRQ that PR18's
770773
position 087 selects, to the IOAPIC input of its window, which changes the interrupts and
771774
discovery tokens that PR17's position 078 records in the snapshot contract and that the slots of
772-
PR18, PR19, and PR23 use. Assigning the later
775+
PR18, PR19, and PR23 use. Position 211 extends the portb exit drain of PR24's position 123 to the
776+
relay that copies the boot console of PR16's position 071 to the terminal, and gives the
777+
management RPC's drain of PR25's position 128 no such relay. Assigning the later
773778
commits therefore means revisiting those PRs, not only appending new ones.
774779

775780
Position 182, by contrast, changes only `disk_blob`, which upstream main and the fork base
@@ -811,6 +816,10 @@ saved states of virtio-console, virtio-fs, and the RTC, the egress policy digest
811816
identity, the virtio-fs slot, and the management RPC's microVM fields, so they follow the PRs that
812817
introduce those ABIs upstream.
813818

819+
Position 211 changes only the microVM CLI's output drain and host consoles, the anonymous serial
820+
pair that they use, and the CLI guide: replaying it on upstream main, `ca6b2b177`, conflicts in
821+
five of its seven files, four of which upstream main lacks, so it follows those PRs upstream.
822+
814823
## PR summary
815824

816825
Each count is of v4.0 commits, which include the fixups that the v3.0 reorganization folded into
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/bin/sh
22
set -eu
3+
exec >@CONSOLE@
34
head -c @PAYLOAD_BYTES@ /dev/zero | tr '\000' x
45
printf '\n@COMPLETION_MARKER@\n'
56
/sbin/nvx-exit @EXIT_CODE@

‎scripts/nvx_tools/microvm_tests.py‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ class MicrovmVirtioDevice(NamedTuple):
348348
CONSOLE_EXIT_COMPLETION_MARKER = b"NVX-CONSOLE-EXIT-OK"
349349
CONSOLE_EXIT_PAYLOAD_BYTES = 64 * 1024
350350
CONSOLE_EXIT_READ_DELAY_SECONDS = 2.0
351+
# Each console-exit run's guest exit status and the console that carries its
352+
# payload: the boot console, on OpenVMM's standard output, or the byte channel,
353+
# on its standard error.
354+
CONSOLE_EXIT_RUNS = ((0, "hvc1"), (37, "hvc0"))
351355
ENDPOINT_POLICY = ("10.0.0.9:8443", "192.0.2.7:443", "10.0.0.9:443")
352356
ENDPOINT_POLICY_BEFORE_MARKER = b"NVX-ENDPOINT-POLICY-BEFORE"
353357
ENDPOINT_POLICY_AFTER_MARKER = b"NVX-ENDPOINT-POLICY-AFTER"
@@ -1606,15 +1610,22 @@ def run_console_exit(
16061610
timeout: float,
16071611
output_dir: Path,
16081612
) -> None:
1613+
"""Check that a guest-requested exit keeps the guest's last output and its
1614+
exit status.
1615+
1616+
Each run restores a guest that writes a payload and a marker to one of its
1617+
consoles and then exits, while the host delays its reads, so that OpenVMM's
1618+
relay of that console lags behind the guest. The exit must write the whole
1619+
output of the boot console and of the byte channel alike."""
16091620
expected = (
16101621
b"x" * CONSOLE_EXIT_PAYLOAD_BYTES
16111622
+ b"\n"
16121623
+ CONSOLE_EXIT_COMPLETION_MARKER
16131624
+ b"\n"
16141625
)
16151626
with tempfile.TemporaryDirectory(prefix="nvx-console-exit-") as temporary:
1616-
for exit_code in (0, 37):
1617-
name = f"console-exit-{processors}vcpu-status-{exit_code}"
1627+
for exit_code, console in CONSOLE_EXIT_RUNS:
1628+
name = f"console-exit-{processors}vcpu-{console}-status-{exit_code}"
16181629
snapshot_path = Path(temporary) / f"snapshot-{exit_code}"
16191630
boot_command = workload_boot_command(
16201631
executable,
@@ -1625,23 +1636,27 @@ def run_console_exit(
16251636
"quiet loglevel=0",
16261637
processors=processors,
16271638
)
1639+
boot_command.extend(("--virtio-console", "console"))
16281640
capture_snapshot(
16291641
[*boot_command, "--snapshot-destination", str(snapshot_path)],
16301642
snapshot_path,
16311643
timeout=timeout,
16321644
processors=processors,
16331645
post_restore_script=_render_script(
16341646
"console-exit.sh.in",
1647+
CONSOLE=f"/dev/{console}",
16351648
PAYLOAD_BYTES=str(CONSOLE_EXIT_PAYLOAD_BYTES),
16361649
COMPLETION_MARKER=CONSOLE_EXIT_COMPLETION_MARKER.decode(),
16371650
EXIT_CODE=str(exit_code),
16381651
),
16391652
log_path=output_dir / f"{name}-capture.log",
16401653
)
1654+
restore_command = snapshot_restore_command(
1655+
executable, backend, snapshot_path, processors=processors
1656+
)
1657+
restore_command.extend(("--virtio-console", "console"))
16411658
with OpenvmmProcess(
1642-
snapshot_restore_command(
1643-
executable, backend, snapshot_path, processors=processors
1644-
),
1659+
restore_command,
16451660
output_dir / f"{name}-restore.log",
16461661
output_read_delay=CONSOLE_EXIT_READ_DELAY_SECONDS,
16471662
) as process:

‎scripts/test_microvm_tests.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5770,8 +5770,15 @@ def test_console_exit_preserves_full_output_and_guest_status(self):
57705770

57715771
self.assertEqual(capture.call_count, 2)
57725772
self.assertEqual(process.call_count, 2)
5773-
for call, code in zip(capture.call_args_list, (0, 37), strict=True):
5773+
# The boot console carries the first run's output, and the byte channel
5774+
# the second's.
5775+
for call, (code, console) in zip(
5776+
capture.call_args_list, ((0, "hvc1"), (37, "hvc0")), strict=True
5777+
):
57745778
self.assertEqual(call.kwargs["processors"], 2)
5779+
self.assertIn(
5780+
f"\nexec >/dev/{console}\n", call.kwargs["post_restore_script"]
5781+
)
57755782
self.assertIn("head -c 65536", call.kwargs["post_restore_script"])
57765783
self.assertIn(
57775784
f"/sbin/nvx-exit {code}\n", call.kwargs["post_restore_script"]
@@ -5780,6 +5787,10 @@ def test_console_exit_preserves_full_output_and_guest_status(self):
57805787
self.assertEqual(call.kwargs["output_read_delay"], 2.0)
57815788
self.assertIn("--restore-snapshot", call.args[0])
57825789
self.assertEqual(call.args[0][call.args[0].index("--processors") + 1], "2")
5790+
for call in (*capture.call_args_list, *process.call_args_list):
5791+
command = call.args[0]
5792+
self.assertEqual(command.count("--virtio-console"), 1)
5793+
self.assertEqual(command[command.index("--virtio-console") + 1], "console")
57835794

57845795
def test_console_exit_rejects_truncation_even_if_marker_survives(self):
57855796
marker = b"\n" + microvm_tests.CONSOLE_EXIT_COMPLETION_MARKER + b"\n"

0 commit comments

Comments
 (0)