Skip to content

nvx-rs: lower adapter plans into the plans of template captures - #884

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
esaurez/841-pr1-template-lowering-20261024
Oct 28, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
esaurez/841-pr1-template-lowering-20261024

Conversation

@esaurez

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

Copy link
Copy Markdown
Contributor

Stacked on #932; this PR retargets when its parent merges.

Chain: #932 → this PR → #887 → #933, on dev; #879, #881, #885, #886, and #877 have merged.

Part of #841 (PR 1 of 3: template lowering).

What and why

A template is a capture of a microVM at OpenVMM's platform tier, after the VM consumed only its invariant configuration, for clones that each bind their own image, upper, and export when they are restored. Before a store can capture one, the host needs the exact VM and options of that capture, and they must meet OpenVMM's rules for the platform tier: a kernel command line of allowlisted tokens only, no tenant data, and nothing that a snapshot cannot hold.

nvx::host::template::TemplatePlan::new lowers an adapter's SandboxPlan (SandboxPlan::adapter) into that plan, without I/O:

  • The placeholder distro, bound through its DistroBinding: the plan's image must be the format and identity that the binding names, and the length that the binding records is the capacity of every clone's image (image_capacity()). Its path must be absolute, since OpenVMM would resolve a relative one against the directory in which it runs; it may hold no comma or semicolon, which OpenVMM's block device options would split; and its name may not end in .vhd, .vhdx, .vmgs, or .iso, which OpenVMM would not attach as a raw image, as a sandbox's validation and PreparedLaunch check them.
  • A dormant scratch device in place of the adapter's upper, which is a clone's: SCRATCH_NAME (scratch.raw) in the capture's transaction directory, a sparse file whose length (scratch_length()) and block sizes the device takes, and which the template does not keep.
  • The dormant filesystem slot: the plan exports nothing, and OpenVMM exposes the slot at every cold boot.
  • No NIC, or one fixed NIC under the template's policy; a policy that lets nothing through attaches none. A policy that allows ingress, or host-loopback access both ways (which forwards need), is refused: OpenVMM publishes the guest's listeners through them as live host attachments, and refuses them for snapshots.
  • TemplateResources: 1, 2, 4, or 8 processors, all online at boot (B = C), and RAM with --memory-capacity, both in blocks of 128 MiB (MEMORY_BLOCK_SIZE). The capacity is always rendered: without it, a capture records no memory expansion, and TemplateManifest::read refuses it.
  • The kernel command line nvx_sandbox=1 (kernel_command_line()), which the caller passes as --cmdline alone, in place of the adapter plan's, which names the root's image identity. OpenVMM writes the other tokens itself, each on its platform allowlist: the console, reboot, and panic settings, nr_cpus=, nvx_snapshot_tier=platform, the virtio devices and the control tty, and the NIC's addresses.
  • The capture's options: --snapshot-tier platform, --snapshot-destination at SNAPSHOT_NAME (snapshot, the name by which a generation of the template store names its snapshot) in the transaction directory, and --snapshot-block-identity generation with the capture's StorageGeneration as --snapshot-generation-id, so that the capture hashes no block device. The template keeps no storage generation: OpenVMM unbinds its distro, and its scratch is fresh.

openvmm_arguments() renders the devices by path, as SandboxPlan::openvmm_arguments does, then capture_arguments(), which a caller that renders the devices' arguments itself can take alone.

The bound-block storage generation is #881's StorageGeneration, rendered with its Display; this PR adds no type of its own for it, and derives it from none of the other namespaces of a template's generations.

Outside nvx-rs/src/host/template/plan.rs and its tests, the change declares and re-exports the module, makes sandbox::reject_disk_path and sandbox::reject_container_path crate-visible, extends the OpenVMM vector test, and updates the docs.

Decisions to review

  • nvx_config=0xd0010000,65536 is on OpenVMM's allowlist, but nothing in NVX defines or reads it, so a template does not pass it. Passing it later changes one constant.
  • The transaction directory must be valid Unicode and absolute, and its path may hold no comma or semicolon, which OpenVMM's block device options would split. The placeholder's path must be absolute too, for the same reason.
  • For the capture itself (PR 2): with --snapshot-destination, OpenVMM opens each block device itself, without following a link at its path (O_NOFOLLOW; FILE_FLAG_OPEN_REPARSE_POINT), and on Windows without sharing write access. A PreparedLaunch's /proc/self/fd/N paths, and on Windows its inherited scratch claim, therefore don't serve a capture at this OpenVMM revision, and the docs here name no such composition. PR 2 therefore hands OpenVMM the paths of the files that it checked, and holds no writable handle on them when OpenVMM starts.

Tests

  • Each token against OpenVMM's allowlist: the template's command line, and the tokens that OpenVMM derives from the rendered arguments (nr_cpus=, nvx_snapshot_tier=, virtnet_ip=, virtnet_gw=), are checked with OpenVMM's own allowlist function, compiled from verbatim excerpts of its source in nvx-rs/tests/vectors/openvmm/. The ignored test of tests/openvmm_vectors.rs, which the NVX CLI jobs run, checks that the excerpts are whole lines of the submodule's files.
  • No tenant data: for both image formats, both uppers, six networks, and every processor count, neither the command line nor the arguments hold the image's identity or the clone's upper, the command line holds no proxy, and the arguments hold only the options of the devices, the resources, and the capture, with ingress and host loopback denied.
  • The rendered arguments, exactly: without a NIC, and with NICs under policies that allow egress, egress with host-loopback access from the guest, and a proxy alone.
  • Each refusal and their order, and the normalization of the transaction directory.

Validation

No VM is needed for this change: everything below ran without one, at this head, with the OpenVMM submodule at e68871cc.

Rebase: this head is rebased onto #932's f28fdca, whose base, with #886 and #877 now merged, retires aci_edge_sandboxes and pins OpenVMM e68871cc. The excerpts of OpenVMM's allowlist are still whole lines of e68871cc's files, and the vectors' copies are unchanged, so the only port is that tests/vectors/README.md now records e68871cc.

  • Windows: in nvx-rs, with NVX_BUNDLE=skip: cargo fmt --all --check; cargo clippy --locked --all-targets --all-features -- -D warnings; cargo clippy --locked --lib -- -D warnings, also with --no-default-features; cargo clippy --locked --all-targets --no-default-features --features <feature> -- -D warnings for host, cli, testing, serde, and bundled; cargo test --locked --all-features; cargo test --locked; cargo build --locked --lib --no-default-features and cargo test --locked --lib --no-default-features, also with --features host; RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --all-features; cargo +1.89 check --locked --all-targets --all-features; and cargo test --locked --test host_modules. From the root, the ignored cargo test --locked --manifest-path nvx-rs/Cargo.toml --no-default-features --test openvmm_vectors -- --ignored. Then the commands of check-nvx-rs for tests/runtime-adapter, protocol-only and host-only, and python scripts/check_markdown_links.py.
  • Linux: the same, plus --features guest, cargo check --locked --all-targets --all-features --target aarch64-apple-darwin, python3 scripts/nvx.py check-guest-binaries, and the runtime adapter's static musl guest configuration.
  • The ignored excerpt test fails when an excerpt's line differs from the submodule's: at e68871cc, the excerpt without its maxcpus= line fails it (checked by editing a copy, then restoring it).
  • CI: run 40574331834, a workflow_dispatch run of CI at tests: capture templates in VMs through the runtime adapter #933's head, which holds this PR, passed: every job that a dispatch runs, among them the microVM, platform, and OpenVMM tests on KVM, MSHV, and WHP.

Left to CI: the full matrix, once this PR targets dev; a stacked PR runs none.

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.

🟢 Approval recommended

The scoped, I/O-free lowering and supporting tests have no identified blocking issues.

No open issues 🎉

What changed in this PR

Adds pure adapter-plan lowering for platform template captures in nvx-rs, the first stage of #841’s capture-and-publication work. This PR prepares capture configuration without launching a VM or publishing a template.

Changes:

  • Adds TemplatePlan and TemplateResources, validating placeholder bindings, resources, network policies, and transaction paths.
  • Renders dormant devices, tenant-free kernel options, and generation-based snapshot arguments.
  • Adds lowering and allowlist tests, verifies copied OpenVMM source excerpts, and documents the API.
File Description
nvx-rs/​tests/​vectors/​README.md Documents OpenVMM source excerpts and verification.
nvx-rs/​tests/​vectors/​openvmm/​platform_command_line_token_is_invariant.rs Copies the platform token allowlist for tests.
nvx-rs/​tests/​vectors/​openvmm/​microvm_control_tty_command_line.rs Copies the control TTY token constant.
nvx-rs/​tests/​openvmm_vectors.rs Checks source excerpts against the submodule.
nvx-rs/​src/​host/​template/​plan/​tests.rs Tests lowering, argument rendering, allowlisting, and refusals.
nvx-rs/​src/​host/​template/​plan.rs Implements capture plans and resource validation.
nvx-rs/​src/​host/​template/​mod.rs Declares and re-exports the plan API.
nvx-rs/​src/​host/​sandbox/​mod.rs Makes disk-path validation available within the crate.
nvx-rs/​src/​host/​mod.rs Documents template-plan lowering.
nvx-rs/​README.md Describes the new API and capture configuration.
doc/​ci.md Documents allowlist excerpt checks in CI.
.github/​workflows/​ci.yml Updates comments describing existing comparison checks.

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/839-pr2-template-store-20261024 branch from 401015b to 8718d50 Compare October 24, 2026 22:20
@esaurez
Enrique Saurez (esaurez) changed the base branch from esaurez/839-pr2-template-store-20261024 to esaurez/839-pr3-leases-20261024 October 24, 2026 22:30
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/841-pr1-template-lowering-20261024 branch from 39ac01d to 79d580b Compare October 24, 2026 22:30
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 24, 2026 22:30

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.

🟢 Approval recommended

The self-contained lowering and argument rendering have focused regression coverage, with no unresolved blocking findings.

No open issues 🎉

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.

🟡 Changes recommended

Placeholder paths bypass lexical validation, allowing capture plans to render block arguments that OpenVMM cannot attach as intended.

1 open issue

Review effort: Balanced

Comment thread nvx-rs/src/host/template/plan.rs
@esaurez
Enrique Saurez (esaurez) changed the base branch from esaurez/839-pr3-leases-20261024 to esaurez/839-pr4-supervisor-seam-20261024 October 25, 2026 00:59
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/841-pr1-template-lowering-20261024 branch from 7f55573 to 290a45b Compare October 25, 2026 00:59
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 25, 2026 01:00

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.

🟢 Approval recommended

The self-contained lowering and validation have focused test coverage, with no unresolved review findings; tests were not rerun.

No open issues 🎉

1 resolved since last review

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.

🟢 Approval recommended

The lowering and validation are consistent with the stated scope, the earlier path-validation issue is fixed, and no blocking issues remain.

No open issues 🎉

Review effort: Balanced

A template is a capture of a microVM that consumed only its invariant
configuration, for clones that each bind their own image, upper, and
export when they are restored. Before a store captures one, the host
needs the exact VM and options of that capture: OpenVMM refuses a
platform-tier capture whose kernel command line holds a token outside its
platform allowlist, and a template must hold no tenant data.

Add nvx::host::template::TemplatePlan, which lowers an adapter's
SandboxPlan without I/O into:

- the placeholder distro, bound through its DistroBinding, whose length
  is the capacity of every clone's image, at an absolute path that
  OpenVMM's block device options hold whole, and that it attaches as a
  raw image, over a dormant scratch device, a sparse file in the capture's
  transaction directory;
- the dormant filesystem slot, and no NIC or one fixed NIC under the
  template's policy, which may allow neither ingress nor host-loopback
  access both ways, since OpenVMM publishes the guest's listeners through
  them as live host attachments, which no snapshot holds;
- TemplateResources: every processor online at boot, and RAM with a
  --memory-capacity, in blocks of 128 MiB;
- the kernel command line nvx_sandbox=1, the one token that the host
  adds to those that OpenVMM writes itself;
- the capture's options: --snapshot-tier platform, a
  --snapshot-destination in the transaction directory, under the name of
  a generation's snapshot in the template store, and
  --snapshot-block-identity generation with the capture's
  StorageGeneration, the bound-block storage generation, so that the
  capture hashes no block device.

The tests check each token of a template's command line with OpenVMM's
own allowlist, compiled from verbatim excerpts of its source in
tests/vectors/openvmm/, which the ignored test of
tests/openvmm_vectors.rs compares with the OpenVMM submodule.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32eea669-a52c-4a53-87fa-521c498537e5
Base automatically changed from esaurez/839-pr4-supervisor-seam-20261024 to dev October 27, 2026 19:41
@esaurez
Enrique Saurez (esaurez) changed the base branch from dev to esaurez/840-pr2-template-mode-20261024 October 27, 2026 19:45
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/841-pr1-template-lowering-20261024 branch from 0468d07 to 77e1660 Compare October 27, 2026 19:46
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 27, 2026 19:47

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.

🟢 Approval recommended

The bounded, I/O-free lowering has comprehensive targeted coverage and no unresolved correctness findings from static review.

No open issues 🎉

Review effort: Balanced

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.

3 participants