Repository navigation
nvx-rs: serve shorter images, and restore clones in RAM without a scratch file - #925
Open
Enrique Saurez (esaurez) wants to merge 4 commits into
Conversation
…atch file A template's restore presents the capacity that its placeholder fixed for the `distro` block device, and OpenVMM reads zeros past the end of a shorter image, so a clone's image need no longer be exactly the placeholder's length. `ClonePlan` admits an image of either format no longer than the placeholder, with its logical and physical block sizes, and refuses a longer one, or one of other block sizes, as before any VMM starts. On Linux a file's physical block size is the I/O block size of its filesystem, so the image must lie on a filesystem of the placeholder's. A scratch image still presents the template's scratch exactly. The restore of a clone in RAM declares the template's scratch absent, with `--restore-absent-scratch`, so that OpenVMM attaches it with no file: its reads return zeros and its writes fail, and the clone's guest never uses it. The host no longer creates the sparse `clone-scratch.raw` in the sandbox's directory: `CloneUpper::Tmpfs` holds nothing, `ClonePlan::scratch_path` returns `None` for a clone in RAM, and the sandbox store loses `CLONE_SCRATCH_NAME` and `clone_scratch_path`. The guest needs no change: it rereads a GPT disk's partition table before it assembles the root, `nvx::gpt` requires only that a disk's backup header lie inside the device, and a clone in RAM looks for no scratch device. The runtime adapter's GPT template now has a placeholder 16 MiB longer than the platform's image, so the clones of either format that it serves are all shorter than the capacity: they find their image's disk at the capacity and their scratch device at the template's scratch length, and read each to its end; nothing in the sandbox of a clone in RAM backs its scratch device, and on Linux its VMM declares the scratch absent. A start refuses an EROFS image and a GPT disk longer than the placeholder, in place of the shorter image that the exact length refused. Part of #848. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The stacked restore changes cross device and guest boundaries, with the final rebased head and shorter-image/absent-scratch VM scenarios still requiring validation on KVM, MSHV, and WHP.
No open issues 🎉
What changed in this PR
This PR implements #848 in the NVX host library, using OpenVMM’s zero-fill support so one template can serve shorter images and restore RAM-backed clones without creating scratch files.
Changes:
- Accept images up to the template’s capacity while preserving block-size checks and exact scratch-image geometry requirements.
- Replace RAM clones’ sparse scratch files with
--restore-absent-scratch, updating the public API and store cleanup. - Expand unit and VM scenarios for shorter images, oversized-image refusals, and file-free scratch devices; update documentation.
| File | Description |
|---|---|
tests/runtime-adapter/tests/vm_clone.rs |
Tests shorter images, presented capacities, and absent scratch backing. |
tests/runtime-adapter/src/clone.rs |
Documents the relaxed image-length contract. |
tests/runtime-adapter/src/bin/runtime-adapter-host.rs |
Updates command documentation for shorter images and RAM uppers. |
tests/runtime-adapter/README.md |
Documents restore behavior and revised VM scenarios. |
nvx-rs/src/host/template/store/leases/tests.rs |
Removes obsolete scratch-file cleanup expectations. |
nvx-rs/src/host/template/store/leases.rs |
Updates lease-release documentation. |
nvx-rs/src/host/template/store.rs |
Documents restores with no scratch backing file. |
nvx-rs/src/host/template/clone/tests/launch.rs |
Checks absent-scratch arguments and scratch-image launches. |
nvx-rs/src/host/template/clone/tests.rs |
Tests capacity admission, geometry refusals, and file-free RAM plans. |
nvx-rs/src/host/template/clone/launch.rs |
Adds the absent-scratch restore flag for RAM uppers. |
nvx-rs/src/host/template/clone.rs |
Relaxes image-length validation and removes RAM scratch-file creation. |
nvx-rs/src/host/store/tests.rs |
Updates clone-file cleanup tests. |
nvx-rs/src/host/store/mod.rs |
Removes the clone scratch filename, path API, and cleanup entry. |
nvx-rs/src/host/store/lifecycle/tests.rs |
Updates removal tests for the reduced store file set. |
nvx-rs/src/host/sandbox/distro.rs |
Documents image capacity and block-size requirements. |
nvx-rs/README.md |
Updates the host-library overview for both restore changes. |
Review effort: Balanced
The template module's overview still said that a clone's plan refuses an image of another length than the placeholder, `SuppliedLength` that a supplied file has another length than the template's device, and `IncompatibleHost` that the sandbox's filesystem may present another geometry than the template's scratch, which only the removed sparse scratch file could. Describe the capacity rule instead: an image longer than the placeholder, or of other block sizes, and a scratch image of another length than the template's scratch. The README's launch also names `--restore-absent-scratch` for a clone in RAM. Part of #848. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
OpenVMM's platform tier admits a canonical maxcpus= token on the kernel command line of a capture whose processors are not all online at boot, since a12d9167. The openvmm_vectors excerpt of the allowlist predates it and failed against that revision. It now admits maxcpus= as the source does, and the vectors' README names the revision that it mirrors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4582f45d-8921-477b-8006-e9c7c709af88 (cherry picked from commit e2bc9ff)
Enrique Saurez (esaurez)
changed the base branch from
esaurez/847-pr2-start-run-suite-20261024
to
esaurez/848-base-snapshot-20261028
October 28, 2026 17:31
Enrique Saurez (esaurez)
marked this pull request as ready for review
October 28, 2026 17:32
6 tasks
…t a scratch file Keep removing the scratch file that earlier versions created for a clone in RAM A sandbox may still hold clone-scratch.raw from a clone that an earlier version launched. Nothing creates the file any more, but the store keeps its name among its files, and a release or a removal removes it, so that such a sandbox can still be released and removed rather than left with a file that the store calls foreign. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #848.
Review base: this PR temporarily targets
esaurez/848-base-snapshot-20261028, a frozen copy of the work it builds on, so that its diff shows only its own commits while the pull requests below it are in review. Before it merges, it is rebased onto #963, #847's second pull request, and retargeted.The review base also holds a commit that pins OpenVMM a12d9167,
dev's pin since #901, whose zero fill (microsoft/openvmm#4749) this change needs. That pin is part of the review base, not of this change, and the final rebase drops it.The last commit, 3ff9b99, is #926's e2bc9ff, unchanged. It brings the base's copy of OpenVMM's platform allowlist,
nvx-rs/tests/vectors/openvmm/platform_command_line_token_is_invariant.rs, up to a12d9167, which also admitsmaxcpus=, and records a12d9167 in the vectors' README, so that the OpenVMM vectors test passes at that pin. It is not part of this change either, and the final rebase drops it too, since #884, below, refreshes the same copy.What and why
Until now, a clone's image had to be exactly as long as the template's placeholder, and each clone in RAM got a sparse scratch file of the template's scratch length, which it never used. OpenVMM now presents the
distrocapacity that aplatformsnapshot recorded over a shorter image, reading zeros past its end, and lets a restore declare a fresh scratch absent (microsoft/openvmm#4749). So one template now serves images of any length up to its capacity, in either format, and clones in RAM restore with no scratch file at all.Images shorter than a template's capacity
ClonePlan::newadmits an image no longer than the template's placeholder (TemplateKey::distro), with its logical and physical block sizes, and refuses, before any VMM starts:supplied-geometry, whatever its length;supplied-length: "the image PATH is N bytes long, longer than the template's placeholder, whose M bytes are the capacity of every clone's image".A file whose length is not a nonzero multiple of 512 bytes stays refused, by
supplied-geometry. On Linux a file's physical block size is its filesystem's I/O block size (st_blksize), so the image must lie on a filesystem of the placeholder's; on Windows it is always 4096 bytes. A scratch image keeps the exact rule: the template's scratch geometry, its length included. Nothing reads, pads, or resizes the image.Clones in RAM without a scratch file
Every restore attaches the template's
scratchblock device, which the template's guest negotiated. For a clone in RAM, the restore now passes--restore-absent-scratchand noscratchblock device, so OpenVMM attaches the scratch at the recorded geometry with no file: reads return zeros, writes fail, and the clone's guest, whose upper is a tmpfs, never uses it. A clone over a scratch image still passes the image as thescratchblock device. So the host creates nothing for a clone in RAM:CloneUpper::Tmpfsis now a unit variant, andClonePlan::scratch_path()returnsOption<&Path>,Nonein RAM.SandboxStore::clone_scratch_path()is gone, and nothing creates the clone's scratch file,clone-scratch.raw, any more. The store still counts its name,nvx::host::store::CLONE_SCRATCH_NAME, among its files,FILE_NAMES, so that a release and a removal remove one that an earlier version left for a clone in RAM, which a removal would otherwise report as foreign.A template's capture keeps its own dormant scratch file, since a cold boot cannot declare it absent.
The guest
No change, as #848 expects. The guest rereads a GPT disk's partition table before it assembles the root (#845), and
nvx::gptrequires only that the primary header's alternate LBA lie inside the device (read_headerinnvx-rs/src/gpt/mod.rs), which holds for a disk shorter than the device; it never reads the backup header. A clone in RAM looks for no scratch device.Tests
nvx-rs unit tests:
supplied-length, and ragged lengths bysupplied-geometry. A template whose placeholder presented other physical blocks serves no image of this filesystem, whatever its length.supplied-geometry.--restore-absent-scratchand noscratchblock device; a clone over a scratch image has no such flag.Runtime adapter VM scenarios, in
vm_clone, on every backend:one_generation_serves_clones_of_either_format_over_either_upper: the GPT template's placeholder is now 16 MiB longer than the platform's EROFS image, so its four clones at once, the EROFS image over a scratch image and in RAM, a GPT disk of the EROFS image's length in RAM, and one as short as its layout allows over a scratch image, are all shorter than the capacity. Each finds its image's disk at the capacity and its scratch device at the template's scratch length, and reads both to their ends. No file in the sandbox of a clone in RAM is as long as the template's scratch, and on Linux its VMM's arguments hold--restore-absent-scratchand noscratchblock device.a_start_refuses_what_the_template_cannot_serve_before_any_vmm_starts: an EROFS image and a GPT disk 4 KiB longer than the placeholder are refused, bysupplied-length, in place of the image 4 KiB shorter that the exact rule refused.An image on a filesystem of another block size has no VM scenario: no CI host offers the suite a second filesystem of another I/O block size without a privileged mount, and Windows presents 4096-byte physical blocks for every file. The nvx-rs tests above cover that refusal.
Guide (#591's numbering)
This PR leaves
doc/designunchanged; #591 takes:platformcapture'sdistropresents the placeholder's length, the capacity of every image that a restore supplies, apart from the image's own length; and every block of a snapshot-capable machine is backed by a file, except a fresh scratch that a restore declares absent.Validation
At 673f222. 09b594a then changed documentation alone, after which fmt,
RUSTDOCFLAGS=-D warnings cargo doc, and thenvx::host::templatetests passed again on Windows.nvx.py verify: all pass.nvx.py check-guest-binaries, and the runtime adapter's guest-only configuration: all pass.--test openvmm_vectors -- --ignored) failed, not because of this change: at the review base's OpenVMM pin,platform_command_line_token_is_invariantadmits one more token,maxcpus=, so the base's copy of it no longer matched. At 3ff9b99, which refreshes the copy, the test passes on Windows, as do fmt, the Markdown link check, and the tests ofnvx::host::template::plan, which compile the copy.nvx.py test-runtime-adapter, on WHP and on Linux with MSHV:aci_edge_sandboxeson Linux and Windows,nvx-rson Linux, and the guest artifacts. Three jobs failed, none because of this PR:nvx-rson Windows, in a test of the base,host::template::store::leases::tests::windows::only_a_file_that_is_gone_or_whose_deletion_is_pending_was_removed: on the hosted runner,platform::file_identityopens a file whose access control list allows no one anything, which the test expects it to refuse (leases/tests.rs:2282). The other 1004 tests passed there. This PR changed that file only to stop naming the clone's scratch file, and now changes only a comment there.aci_edge_sandboxeson Linux and Windows,nvx-rson Linux, and the guest artifacts.nvx-rson Windows, in the base's test, as in the first run, while the other 1004 tests pass; and the required status check, which reports those.devalone.esaurez/848-ci-20261028: 3ff9b99 with two commits ofdevthat the review base lacks, 88cc894, which pins OpenVMM e68871cc, the virtio-fs fix of OpenVMM: virtio-fs queues never serve requests after a restore behind the post-restore input gate #904, and 3fbaf8a, which fixes the base's leases test above. 35 jobs passed, 3 failed, and the same 4 as in the second run were skipped.nvx-rson Windows, and guests and sandboxes on WHP, with all 153 of the runtime adapter's scenarios.a_clone_maps_its_export_beneath_its_root_before_its_sandbox_startsanda_run_s_execution_finds_its_shares_beneath_the_root, in which the sandboxed workload's writes through a read-write export are refused, withPermission denied, on the hosted Linux runners, which run the suite unprivileged. Both are scenarios of the base, whose clones use a scratch image and an image as long as the placeholder, and both pass on WHP and, as root, on Linux with MSHV. The required status check reports those.nvx-rslibrary tests, andRUSTDOCFLAGS=-D warnings cargo docpass on Windows, and the same, without the docs, pass on Linux (x86_64). On Linux, one test of the base's guest code,guest::sys::rooted::tests::paths_resolve_only_beneath_their_directory_and_through_no_link, failed once, withEAGAINfora/../..where it expectsEXDEV, whichopenat2withRESOLVE_BENEATHreturns when a rename races with the lookup, as other tests' renames may; it passed in a full rerun and in 20 runs on its own. This PR changes no guest code.a_clone_whose_activation_fails_ends_before_it_is_readytwice found the log of its VMM still held after the VMM had exited, which a handle that a concurrent spawn inherits would explain, in the base's code.