Skip to content

tests: launch the runtime adapter's VMs and reattach after a restart - #718

Merged
Enrique Saurez (esaurez) merged 2 commits into
devfrom
esaurez/a1-host-h5-adapter-vm-20261020
Oct 22, 2026
Merged

Enrique Saurez (esaurez) merged 2 commits into
devfrom
esaurez/a1-host-h5-adapter-vm-20261020

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

The runtime adapter's host program now drives a VM's life through NVX's supervisor, as an orchestrator outside NVX does: it launches a VM whose initramfs runs the adapter's guest agent in place of NVX's managed agent, and later processes of it reattach to the VM after a simulated restart, attach a fresh session, and stop it, through public APIs only. Ignored tests run these scenarios on a real hypervisor, and the microVM guest and sandbox jobs run them on every backend.

Follows #671, which added the adapter's host program.

Part of #620
Refs #535

What changed

  • nvx::guest::terminal, in its own commit. A public guest library for the terminal on which a guest agent serves its control session: Terminal::open opens it for reading and writing, waiting while the kernel sets it up during boot (it retries ENOENT, ENXIO, ENODEV, and EIO every 50 ms for 30 s), configure_raw puts it in raw mode with CLOCAL, and drain waits until it has sent what the agent wrote. NVX's managed agent now opens its control terminal through it, and puts it in raw mode and drains it through the functions that the library wraps, so a guest agent outside NVX no longer repeats those settings. Unit tests open pseudo-terminals: one that the kernel has not set up yet, every byte value through raw mode, a file that is no terminal, and the errors that end the retries. The crate docs, nvx-rs/README.md, and doc/project-structure.md list it with the other guest libraries.
  • The guest agent on the control tty. guest::serve_terminal opens a tty with nvx::guest::terminal, as the managed agent does, serves the session across resets, and drains the tty once serving ends. runtime-adapter-guest init TTY runs it, and as PID 1 ends the VM through /sbin/nvx-exit, with 0 once the host stopped it and 1 otherwise, as the managed agent does. The command's name is defined once, as runtime_adapter::GUEST_INIT_COMMAND beside GUEST_SERVE_COMMAND, and the binary, its usage message, the launcher below, and the tests use it.
  • The guest agent in place of the managed agent. NVX's /init runs exec /sbin/nvx-managed-agent /dev/hvc2 - nvx-managed UID GID NAME HOME as PID 1. runtime_adapter::initramfs::write copies NVX's initramfs and appends a newc archive that Linux unpacks over it: the guest agent, a /bin/sh launcher that runs runtime-adapter-guest init "$1", and a symbolic link from /sbin/nvx-managed-agent to the launcher. It is a link because Linux unlinks a file whose type an archive changes, rather than truncating an inode that other names could share. The archive starts at a multiple of 4 bytes, after zero padding. NVX's own images don't change.
  • The runtime identity from NVX. runtime_adapter::runtime::Runtime::from_dir finds the artifacts with nvx::host::artifacts::Artifacts::from_dir, and takes their runtime identity from ArtifactDir::runtime_identity (nvx-rs: prepare the CLI's move onto the store and supervisor #675), which names each artifact by the SHA-256 digest that the install metadata beside it records, never from its contents: a release's SHA256SUMS, or the build provenance in a checkout's build/, where nvx.py download and CI put it. The adapter changes only the identity of the initramfs, which names NVX's initramfs and the guest agent's build, sha256:DIGEST+runtime-adapter-guest/BUILD, with the crate's version unless --guest-build names another, so a host program that ships another build of the guest agent runs another runtime.
  • runtime-adapter-host launch, reattach, and stop on a sandbox store, with NVX's artifacts in NVX_ARTIFACTS_DIR:
    • launch builds a LaunchSpec for the blockless managed root with the guest agent's initramfs, which it writes once the launch has checked that no VM of the sandbox runs, launches through supervisor::launch, connects with nvx::host::session::connect, attaches, exchanges a Ping and the longest Echo, which spans five records each way, commits, and exits.
    • reattach takes the lock and calls supervisor::reattach, which holds the VMM although it is not the process's child. It reads the capability from the store, attaches a fresh session, in a new epoch, and exchanges a Ping.
    • stop does the same, then calls Vmm::stop with the adapter's Stop, answered with Stopped, as the graceful step, waits for the exit, and checks the outcome report: success, status 0, and a complete teardown. A VM whose guest agent doesn't confirm is terminated, and the command fails.
    • A different runtime identity reports mismatched with the parts that differ, exits with 3, and signals nothing.
  • Tests:
    • tests/vm_restart.rs, ignored, reads the backend through nvx::Hypervisor::from_env_or_default. One test launches, then reattaches from a second process (the same VMM and broker instance, a later epoch), then stops from a third (graceful, success 0, a complete teardown, and no runtime record left). The other reattaches with another guest build: mismatched in initramfs, the VM still runs under the same runtime record, and the right runtime then stops it gracefully. Each command is a separate process with a timeout. A failed test ends the VM that it left, and keeps the sandbox's directory, whose path it prints, unless it can confirm that the VM ended.
    • tests/initramfs.rs and tests/runtime.rs, host-only, run on Linux and Windows: the archive's entries, modes, owners, and padding; the copy and its alignment for every base length modulo 4; and the runtime identity of a checkout and of a release, which differs from NVX's in the initramfs alone, of another build of the guest agent, which differs there too, of builds that no artifact identity can hold, and of missing install metadata, which fails as NVX reports it.
    • tests/guest_session.rs: through a relay to a pseudo-terminal, serve_terminal and runtime-adapter-guest init pass an Echo of every byte value, so the tty is in raw mode, and init fails at once on a path that is no tty. A failed test kills the guest binary that it started, and the pseudo-terminal's descriptors are closed on exec, so that no process holds its master end.
  • CI: the microVM guest and sandbox jobs, nvx-microvm-guests-{kvm,mshv,whp}, run the scenarios, and the default and debug-kernel suites neither build nor run them. The hook in run-nvx-microvm-tests.yml sets NVX_ARTIFACTS_DIR to the checkout, into which the job downloads the artifacts and their provenance, and RUNTIME_ADAPTER_GUEST to the guest agent. The Linux jobs build the guest agent as a static musl executable first. distro-artifacts, which uploads the Ubuntu and Azure Linux images that the Windows job takes, also builds the guest agent on Linux and uploads it as runtime-adapter-guest, which that job downloads. In 8 of 10 recent runs that I sampled, distro-artifacts finished 3.5 to 8.4 minutes before nvx-cli-windows, which the Windows job also needs, so this build, about a minute, seldom delays it. The build runs whenever distro-artifacts runs, because a job may read only its own change detector's output, as test_ci_jobs_follow_their_change_detector_output checks. scripts/test_nvx_tools.py checks this wiring, and doc/ci.md describes it.
  • Dependencies: no new package. The guest agent's tests open pseudo-terminals with nix (term), a Linux dev-dependency that nvx already pulls in, and the guest-only build still pulls in no host-only dependency.
  • Docs: the adapter's README, nvx-rs/README.md, the nvx crate docs, doc/ci.md, and doc/project-structure.md.

Validation

All of the following ran on this head's tree; the last push only restored a blank line between two jobs in ci.yml, after which the workflow tests of scripts/test_nvx_tools.py ran again.

On Windows 11, in tests/runtime-adapter, for the protocol-only and host-only configurations:

  • cargo fmt --all --check
  • cargo clippy --locked --all-targets -- -D warnings, and the same with --features host
  • cargo build --locked and cargo test --locked, and the same with --features host
  • cargo +1.89 check --locked --all-targets, and the same with --features host
  • RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --features host

On an Azure Linux 3.0 host, the Linux steps of check-nvx-rs and check-aci-edge-sandboxes:

  • in nvx-rs: formatting, both Clippy runs, cargo test --locked --all-features (886 tests) and cargo test --locked (424), the bundled-artifacts test, rustdoc, the MSRV check, the aarch64-apple-darwin check, and nvx.py check-guest-binaries, build-guest-binaries, and test-guest-binaries
  • in tests/runtime-adapter: Clippy, the build, the tests, and the MSRV check of all three configurations, the guest-only one for x86_64-unknown-linux-musl; formatting; rustdoc of the host-only and guest-only configurations; and the release build of the guest agent
  • in aci_edge_sandboxes, which builds on nvx: formatting, both Clippy runs, both test runs, nvx.py test-agent-bundling, rustdoc, the MSRV check, and the aarch64-apple-darwin check

From the checkout's root, on Windows 11, with Git for Windows' bash first on PATH, as on the hosted runners:

  • the validate-nvx steps: compileall, the unittest run of its eleven suites (1250 tests, 39 skipped), test_hosts.py, and each nvx.py --help that it runs
  • python -m ruff check and python -m ruff format --check on scripts .github/specula, and python -m pyright --pythonplatform Linux and --pythonplatform Windows, with requirements-dev.txt
  • python scripts/check_markdown_links.py and python scripts/nvx.py verify

scripts/test_adversarial.py fails four of its process-containment tests on this machine at dev's head too, so it is left to CI.

On real hypervisors, cargo test --locked --features host --test vm_restart -- --ignored passed both tests, and left no VM or sandbox directory behind, with the guest agent from cargo build --locked --release --features guest --target x86_64-unknown-linux-musl --bin runtime-adapter-guest:

  • WHP on Windows 11, with the artifacts of release v0.1.0-dev.8483e3757469, which python scripts\nvx.py download installed in the checkout, so the identity came from their build provenance.
  • MSHV on the Azure Linux 3.0 host, as root, with the same release extracted, so the identity came from its SHA256SUMS. That host's CPU cannot present NVX's built-in CPU profiles, so a local wrapper around openvmm added --cpu-profile host. The CI runners use the built-in profiles.
  • KVM ran them before this branch was rebased onto dev, with the adapter's earlier identity code. CI runs this head on KVM.

With injected failures, on an earlier head whose code for them is unchanged:

  • a restart test that fails after the launch ends the VM and removes its directory, and keeps the directory when it cannot confirm that the VM ended;
  • a tty test that fails while the guest binary waits leaves no process behind, through the kill alone and through the close-on-exec descriptors alone, while a control with neither leaves one;
  • without cfmakeraw in the raw-mode settings, the library's test that passes every byte value fails, and so do both of the guest agent's tty tests.

Left to CI: the microVM guest and sandbox jobs on KVM, MSHV, and WHP with the built-in CPU profiles, and distro-artifacts' build and upload of the guest agent.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform VM recovery and guest PID 1 shutdown warrant final human review alongside backend CI results.

No open issues 🎉

What changed in this PR

Extends the independent runtime adapter to exercise NVX’s public supervisor APIs for VM launch, reattachment after an orchestrator restart, and shutdown, advancing #620.

Changes:

  • Adds guest control-terminal serving, initramfs customization, and provenance-based runtime identity.
  • Adds host lifecycle commands and tests for restart, identity mismatch, and graceful shutdown.
  • Wires VM scenarios into backend CI and documents their use.
File Description
tests/​runtime-adapter/​tests/​vm_restart.rs Tests cross-process reattachment, mismatch handling, and shutdown.
tests/​runtime-adapter/​tests/​runtime.rs Tests provenance-based runtime identities.
tests/​runtime-adapter/​tests/​initramfs.rs Tests archive contents, alignment, and copying.
tests/​runtime-adapter/​tests/​guest_session.rs Adds terminal tests and child-process cleanup.
tests/​runtime-adapter/​src/​runtime.rs Reads artifact identities from provenance.
tests/​runtime-adapter/​src/​lib.rs Exposes new modules and the guest init command.
tests/​runtime-adapter/​src/​initramfs.rs Builds the guest-agent initramfs overlay.
tests/​runtime-adapter/​src/​guest.rs Adds raw-terminal session serving.
tests/​runtime-adapter/​src/​bin/​runtime-adapter-host.rs Adds launch, reattach, and stop commands.
tests/​runtime-adapter/​src/​bin/​runtime-adapter-guest.rs Adds terminal init and PID 1 shutdown handling.
tests/​runtime-adapter/​README.md Documents lifecycle commands and tests.
tests/​runtime-adapter/​Cargo.toml Enables feature-scoped JSON and terminal dependencies.
tests/​runtime-adapter/​Cargo.lock Records direct adapter dependencies.
scripts/​test_nvx_tools.py Checks guest-agent CI wiring.
doc/​project-structure.md Describes adapter lifecycle coverage.
doc/​ci.md Documents new validation and backend scenarios.
.github/​workflows/​run-nvx-microvm-tests.yml Supplies guest agents and artifacts to scenarios.
.github/​workflows/​ci.yml Builds and uploads the guest agent for Windows jobs.

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform VM lifecycle and cleanup behavior, backend CI results, and the planned #675 identity integration need final human confirmation.

No open issues 🎉

Review effort: Balanced

Base automatically changed from esaurez/a1-host-h5-adapter-host-20261020 to dev October 22, 2026 21:53
`nvx::guest::terminal::Terminal` opens the terminal on which a guest agent
serves its control session, waiting while the kernel sets it up during
boot, puts it in raw mode, and drains it, so that a guest agent outside NVX
serves its session on that terminal as NVX's managed agent does, rather
than repeat its settings. The managed agent opens its control terminal
through it, and configures and drains it through the same functions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 76c7c1bb-20fe-4724-bbc0-dfb01bbec828
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a1-host-h5-adapter-vm-20261020 branch from 0d3909c to 73ee141 Compare October 22, 2026 22:31
Copilot AI balanced review requested due to automatic review settings October 22, 2026 22:31
The runtime adapter's host program now launches a VM through NVX's
supervisor, with the adapter's guest agent in place of NVX's managed agent,
and later processes of it reattach to the VM and stop it, as an orchestrator
does after it restarts, through public APIs only.

- `runtime-adapter-guest init TTY` serves the control tty, which
  `guest::serve_terminal` opens with `nvx::guest::terminal`, as the managed
  agent does, and as PID 1 ends the VM through `/sbin/nvx-exit`.
- `runtime_adapter::initramfs` copies NVX's initramfs and appends a `newc`
  archive with the guest agent, a launcher script, and a symbolic link that
  replaces `/sbin/nvx-managed-agent`. NVX's own images don't change.
- `runtime_adapter::runtime` takes the runtime identity of NVX's artifacts
  from `ArtifactDir::runtime_identity`, which reads the install metadata
  beside them and never hashes them, and names the guest agent's build in
  the identity of the initramfs.
- `runtime-adapter-host launch`, `reattach`, and `stop` work on a sandbox
  store: the launch attaches, exchanges a Ping and an Echo, and commits; a
  later process reattaches to the VM, which is not its child, attaches in a
  new epoch, and stops it with the adapter's Stop as the graceful step. A
  different runtime identity finds the VM mismatched and signals nothing.
- `tests/vm_restart.rs` runs these scenarios on a real hypervisor, as
  ignored tests that the microVM guest and sandbox jobs run on every
  backend. The Linux jobs build the guest agent, and distro-artifacts builds
  it on Linux for the Windows job.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 76c7c1bb-20fe-4724-bbc0-dfb01bbec828
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a1-host-h5-adapter-vm-20261020 branch from 73ee141 to 631406b Compare October 22, 2026 22:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-process VM recovery, PID 1 shutdown, and forced cleanup across three backends warrant final human review and confirmation of the remaining CI results.

No open issues 🎉

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 22, 2026 22:35
@esaurez
Enrique Saurez (esaurez) marked this pull request as ready for review October 22, 2026 22:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

PID 1 shutdown, cross-process VM recovery, and cross-platform artifact delivery warrant final human review with successful exact-head CI across all three backends.

No open issues 🎉

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) merged commit 3bd0c15 into dev Oct 22, 2026
75 checks passed
@esaurez
Enrique Saurez (esaurez) deleted the esaurez/a1-host-h5-adapter-vm-20261020 branch October 22, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants