Skip to content

sandbox: record the network's options and build its policy at each start (SL3.10c) - #1501

Draft
Pedro Henrique Penna (ppenna) wants to merge 8 commits into
feature/nvx-refactorfrom
agents/slim-sl3-10c-network-record
Draft

Pedro Henrique Penna (ppenna) wants to merge 8 commits into
feature/nvx-refactorfrom
agents/slim-sl3-10c-network-record

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1112: SL3.10b, step 2 of 3 (the CLI no longer uses the old launch, store or config APIs). Needs #1491 and #1489 (with #1486 and #1487), and is stacked on #1497.

What and why

SL3.10b removes the last uses of nvx's old launch, store and config APIs from the CLI's four shared sandbox modules: host/cli/sandbox/{mod,options,state}.rs and sandbox/lifecycle/mod.rs. Its measured scope came to about ten times its estimate of 20 core lines, since SandboxLaunch, SandboxSettings, SandboxUpper and SandboxNetwork still carry provision, start and the sandbox's record. The split of SL3.10b into three stacked pull requests, each with one goal, was approved for Pedro Henrique Penna (@ppenna):

  1. sandbox: name the runtime identity and gateway without the old store and plan (SL3.10b) #1497: the store and old-plan leftovers.
  2. This pull request: the sandbox network moves to NetworkConfig, and the record to format 10.
  3. sandbox: record the launch's options without the old launch profile (SL3.10d) #1503 (SL3.10d), stacked on this one: SandboxLaunch, SandboxSettings and SandboxUpper leave the four modules for the CLI's record and a VmConfig.

This step:

  • sandbox_network (sandbox/options.rs) returns the host::config::NetworkConfig that the VMs take, rather than the old plan's host::sandbox::SandboxNetwork, after the checks that SandboxNetwork::new made, in the same order and with the same messages: the guest network, and that OpenVMM's options can express the policy (host::network::openvmm_arguments). nvx-rs run takes it directly, and start no longer converts one into the other.
  • config.json records the CLI's own network options, as a NetworkRecord, in place of SandboxNetwork's serde form, which it does not copy: --net, --network-egress, --network-ingress, --host-loopback, --network-proxy and the --host-loopback-forward options as given, and the egress rules in OpenVMM's form, as the rule options gave them or a policy file compiled them, so no start reads that file again. Each start, and the record that sandbox run writes and reads back, rebuilds the options with the parser's own receiver, NetworkOptions::take, and builds the network from them with sandbox_network, as provision did. provision and sandbox run pass the record in ProvisionConfig.
  • The record requires every field, null for an option not given, and Config::new builds the network as it reads the record, so a record whose options the parser or sandbox_network refuses fails as sandbox configuration is malformed when it is read, as the old plan's network did.

Record format 10. The record changes form, so the configuration moves from format 9 to format 10. #1076 is not backward compatible ("a deploy starts from fresh state"), and nothing that this workstream changes is deployed, so a format-9 record is refused rather than migrated, as format 8 was: start fails with format-unsupported, sandbox configuration has an unsupported format: PATH; deprovision the sandbox and provision it again, and deprovision still removes the sandbox. The advice is new, for every unsupported format. The README's state-directory section and doc/run.md say so. Format 9 and restored sandboxes exist only on feature/nvx-refactor, which is not deployed: dev records formats 1, 7 and 8 and restores no sandbox. So deprovision does not read an older record either, not even the source of a format-9 sandbox restored by a build of this workstream, whose view of its snapshot it removes only once it reads that source; a cold format-9 sandbox, which has no view, deprovisions.

Size. 119 core lines, 6 counted files: sandbox/state.rs 84, sandbox/mod.rs 16, sandbox/options.rs 11, sandbox/lifecycle/mod.rs 3, sandbox/lifecycle/start.rs 3 and cli/run.rs 2. The public API, BOOT_CONTRACT_VERSION and CONTROL_CONTRACT_REVISION do not change.

Stack. #1491 (SL3.10a) and #1489 (SL3.8b, on #1487 and #1486) have not merged, so this branch sits on a temporary local base: origin/agents/slim-sl3-10a-downloads-session (5fda1863414), with SL3.8a1 (8c9847b7b09), SL3.8a2 (bf63b6aaf7f) and SL3.8b (d2a2ef3266d) cherry-picked on top, conflicts resolved (f51ed641759, cd5f2f841a6, 2b4bec6c983), and one local stand-in, 0ecbfe0750c, that deletes sandbox::one_shot and Windows spawn_foreground/standard_handle, which SL3.4b and SL3.8b leave unused and SL3.8b's own pull request deletes. Then #1497's commit (e908c5b9e44), and this one. Review only the last commit, d5fdcc648f0; the branch is rebased onto the merged inputs before it leaves Draft.

Validation

At this head (d5fdcc648f0), on the temporary base above, from this branch's action definitions:

Check Result
Windows, check-nvx-rs: cargo fmt --check; Clippy with -D warnings (--all-targets --all-features, --lib, --lib --no-default-features, --lib --bins --features cli, --lib --bins --no-default-features --features testing); cargo test --all-features (lib 1478, cli 110) and cargo test; the --lib --no-default-features build and tests; the bundled-artifacts test; cargo doc with -D warnings; MSRV 1.89; the runtime adapter's protocol-only and host-only configurations Passed, but for host::cli::snapshots::capture::tests::a_capture_ack_and_successful_output_without_timely_exit_are_stopped_but_never_finished, which fails at the temporary base too (SL3.10a's; #1495 fixes it)
Windows, validate-nvx: compileall, the 22 scripts/test_*.py modules (1830 tests), test_adversarial.py, test_hosts.py, nvx.py help Passed. A first run caught the fake nvx-rs of the sandbox-ram-upper tests, which still recorded format 9; fixed here
Windows: check_test_layout.py, check_markdown_links.py Passed
Linux (nvx-kvm-test, Azure Linux 3, KVM, virtual machine): the check-nvx-rs commands above, the public API, the macOS check, the guest binaries' lint, tests, build and checks, and the runtime adapter's three configurations; validate-nvx Passed, but for the 28 guest:: unit tests of cargo test --all-features (lib 2291 passed, cli 119) and the 26 of nvx.py check-guest-binaries, which fail on that host at the base too; none touches the CLI
Linux, KVM: nvx.py test-microvm --backend kvm with the public sandbox scenarios managed-exec-config, sandbox-lifecycle, sandbox-proxy-only and sandbox-ram-upper Passed: nvx-rs and the Alpine initramfs built at this head; OpenVMM, the kernel and the scratch template of release v0.1.0-dev.53ad09678bc1, which pins the same OpenVMM; the Ubuntu distro layer of feature/nvx-refactor d4edeeb9ab9's CI run, whose guest inputs this branch keeps

A code review found that a hand-edited record could leave out a network field, such as egress, which then defaulted to allow, and that a network that its checks refuse failed only later, with another code. The record now requires every field and builds the network when it is read, as above, and malformed_configuration covers both.

Left to CI: MSHV and WHP.

Tests modified

check_test_changes.py --base e908c5b9e44 lists 12 changes to existing tests, for the record's new form and format:

  • minimal_config and configurations (tests/cli/lifecycle.rs): the expected records take format 10, and the network and proxy-only cases the NetworkRecord; their starts still launch OpenVMM with the same network options.
  • malformed_configuration: the format cases take 10 ({"format": 11}, {"format": 10.0}), and four new cases refuse network records without a field, of a gateway address, with another egress, or with a broken rule as malformed. Without the check when the record is read, start accepted the record without egress, and allowed egress.
  • configurations_of_every_format_start_and_exec (tests/cli/formats.rs): asserts format 10. old_configuration_formats_are_refused_without_migration: adds format 9 and the advice, which it lacked before.
  • RamUpperAcceptance.check_managed (scripts/nvx_tools/sandbox_ram_upper_tests.py) expects format 10, and the fake nvx-rs of its tests (_FakeRamUpperSandboxCli._provision, scripts/test_microvm_tests.py) records 10, and 9 as its old format.
  • a_one_off_restore_installs_the_network_options_of_run_as_its_own_policy (start/tests.rs) takes the NetworkConfig directly; it and deprovision_removes_a_restored_sandboxs_view_only_under_its_snapshots_lock, run_checks_again_that_its_shares_do_not_overlap and restored_sandbox_records_round_trip_only_their_own_source give ProvisionConfig its network: None, and the imports follow.

Enrique Saurez and others added 8 commits November 11, 2026 12:16
The download started its tools with supervisor::spawn_command, which
holds the old supervisor's spawn lock while it creates a process, so
that on Windows the tool inherits no handle of a VMM that another
thread of the process starts meanwhile (#966). The CLI is to stop using
the old supervisor, and needs no lock for its tools: run, sandbox run
and sandbox start download before they launch, and no CLI command
starts a VM on another thread while it downloads.

tar, git and curl now start with Command::spawn, with the same standard
streams, environment and working directory as before. Their errors stay
as they were: a tool that does not run fails the download as
unavailable, naming the program; a tool that fails reports its standard
error; and the CLI checks for Ctrl-C once a tool exits, which reaches
the tools as before, since they still share the CLI's console and
process group.

The library's tests, though, run the download beside tests that launch
stand-in VMs, so #966's protection moves into them: each archive and
GitHub test that runs tar or curl holds the tests' spawn lock, which
host::tests::hold_spawns returns, while it runs them, and the archive
tests' helper that packages a release spawns its own tar with
host::tests::spawn_locked, which therefore builds wherever the CLI
does. A new GitHub test checks the error of a curl that does not run,
which the changed spawn produces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A managed cold run waits for its control endpoint with
Session::connect_launched_cancellable, which ends the wait once OpenVMM
exits, rather than at its timeout. It took the old supervisor's child
type, spawn::Launched, which host::vm::Launched lent it through
child_mut, and polled that child's status.

It now takes the verified process of the run's host::vm::Launched,
beside the endpoint and capability that the launch supplies, and asks
that process whether it has exited. The process is the same OpenVMM,
held through its pidfd or process handle, so the wait notices its exit
as the child's status did, without reaping it: the foreground still
reaps it through wait_status. A check that fails still keeps the wait
going until the timeout. child_mut becomes private to host::vm, whose
wait_status alone uses it, and the session's comments no longer name
host::instance. sandbox exec and sandbox stop attach through
Session::reattach, which verifies a saved VM through host::vm, and do
not change.

A new test gives the wait a process that runs and then exits: the wait
goes on while the process runs, and ends as a closed endpoint once it
exits, rather than at its timeout, on Linux and Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A sandbox's configuration can now name a snapshot of the CLI's named
snapshot store: the snapshot, the store, and the workload that each
restore runs, an entrypoint and its arguments. Each `sandbox start` of
such a sandbox restores the snapshot in place of a cold boot, and
`deprovision` removes the sandbox's view of the snapshot. SL3.8a2's
`snapshot restore` provisions such sandboxes; until then `provision`
records no snapshot.

A start of a restored sandbox opens the store, takes the snapshot's
exclusive lock, removes the view of an earlier start, and creates the
VM with `host::vm::create` from the snapshot, with the snapshot's
online processors, through the store's launch journal. The blocking
restore driver then receives the restore-ready event of the verified
process, activates the guest with the activation record of the start's
own network policy, which gives the guest its DNS server and listener
announcements, sends the sandbox's mapping table, and starts the
workload. Closing the session that the driver returns at once can lose
the workload's Start, as real KVM and MSHV restores showed, so the
start keeps it until the workload ends, forwards its output, and fails
unless it exits with status 0 within --timeout. The lock goes once the
start ends, or once a failed start has verifiably ended the VM. Ctrl-C
and --timeout cancel the driver and the wait. On Windows, OpenVMM opens
a restore's scratch only while no other handle may write it, so the
start closes the scratch claim's writable handle, and the VM inherits
the claim's guard instead. A start whose command dies before it commits
leaves its journal, whose recovery by the next command ends the VM. A
spawn that cannot confirm that its VM ended has that recovery end the
VM of a restore before the snapshot's lock goes; a start that cannot
confirm that its VM ended at all leaves the journal to the next
command. A closed control endpoint proves no exit, so, as for a cold
start, OpenVMM then has the grace to exit by itself before the start
ends it. A cold start takes the same launch path as before.

A restore's guest accepts only a fresh upper, whose `upper` and `work`
directories are missing or empty, so a later start of a restored
sandbox with a scratch image refuses before it launches once the view
of an earlier start shows that it gave the image to a guest. One with
an upper in RAM restores again.

`deprovision` removes a restored sandbox's `openvmm/` view, and on
Linux its restore-ready socket, under the snapshot's lock, and refuses
a view that is a link or holds anything else. Before a start or
deprovision removes anything, it refuses a state directory in the
snapshot store, once links are resolved, since the view there could be
a snapshot's own files. A busy snapshot lock names the snapshot.

The record is SL3.5a's typed configuration, format 9, which keeps
`source_snapshot` and now records `source_store` and `workload` beside
it, only for a restored sandbox. `Config::source` reads them back for
start and deprovision, which ends the `expect(dead_code)` hook of the
name's accessor, and `serialize_launch` takes the snapshot to record
in place of `serialize_launch_with_snapshot`. The start is the
production caller of SL4.3's restore driver, which ends its hook and
its test-layout allowance.

This is the first half of SL3.8a, which adapts the first implementation
on esaurez/slim-3.2.2-sl3.8a-private-20261108 (e7d77fc), its restore
start, source record, view removal under the lock, and restore-ready
socket cleanup, to the CLI-owned lifecycle of SL3.5b, the typed
configuration of SL3.5a, the restore creation of SL2.16, and the
restore driver of SL4.3.

Tests: fake-backed restores, whose checked launch spawns a fake OpenVMM
and guest in place of OpenVMM, cover EROFS and GPT images with scratch
and RAM uppers, the restore's own DNS server and published listeners in
the activation record, the mapping table, and the workload; concurrent
restores of one snapshot, the second of which spawns its VM only once
the first one's has exited; failed restores, including a workload that
exits with status 3, which end their VM and launch before the snapshot
goes free, and a guest that hangs up after the workload's Start, whose
VM exits by itself within its grace; a spawn that cannot confirm that
its VM ended, whose VM the journal's recovery ends before the snapshot
goes free; a later start, which restores again with an upper in RAM
and refuses a scratch image that an earlier start gave to a guest; a
command that dies before Ready, whose VM the next lock's recovery ends
without any activation; deprovision, which waits for the snapshot's
lock before it removes the view; the view removal's refusals, a VM
directory in the store among them; and the record's source.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`snapshot restore NAME --store-dir STORE --state-dir STATE` provisions
a managed sandbox with the options of `sandbox provision` that a
restore takes, and starts it from the named snapshot that `snapshot
capture` published. The sandbox's configuration records the snapshot,
its store, and its workload, the entrypoint and its arguments, so each
later `sandbox start` restores the snapshot again, as SL3.8a1's start
does. The command takes provision's `--layer-format` and
`--ram-upper-percent`, so it restores an EROFS or a GPT image, with a
scratch upper or a RAM upper and its cap, but none of the options that
only an execution or a cold boot takes, which it leaves unrecognized.

It opens the snapshot under its lock first, so a missing snapshot
provisions nothing, and refuses a state directory in the snapshot
store, whose files a start or deprovision could take for a view. A
sandbox whose start fails stays provisioned, as after `sandbox
provision`. No index of a snapshot's sandboxes or record of a restore
in progress exists.

`lifecycle::provision` takes the snapshot that it records, which only
`snapshot restore` passes. The `snapshot` subcommand parser moves from
the capture module to the snapshots module, which dispatches both
commands.

This is the second half of SL3.8a, from the first implementation on
esaurez/slim-3.2.2-sl3.8a-private-20261108 (e7d77fc).

Tests: the snapshot parser, which selects capture or a restore with the
options of a provision, provision's image format and RAM upper cap
among them, requires the snapshot, its store and the state directory,
and leaves the options of an execution or a cold boot unrecognized;
the capture parser, which now parses only the options after `capture`;
and the refusal of a state directory in the store before anything is
provisioned.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`run --restore-snapshot PATH` handed a raw OpenVMM snapshot directory to
OpenVMM with the options of a cold boot, outside the store, the locks,
and the restore driver of the named snapshots that `snapshot capture`
captures and `snapshot restore` restores. It now takes the name of a
snapshot of `--store-dir` and restores it once, through the path of
`snapshot restore`: `sandbox::restore_once` provisions a managed
sandbox in `--state-dir`, starts it from the snapshot under the
snapshot's lock, through the launch journal and the blocking restore
driver, which forwards the workload's output, stops the VM once the
workload has exited with status 0, deprovisions the sandbox once it is
provisioned, whatever happened, and ends with the workload's status.

The restore inputs are options of `snapshot restore`, taken from its
own option list and parsed by `sandbox` for both: `--state-dir`,
`--layer`, `--layer-format`, `--scratch`, `--upper`,
`--ram-upper-percent`, `--entrypoint`, `--arg`, `--hostname`,
`--workload-user`, and `--timeout`, so a restore of `run` takes none
that `snapshot restore` refuses, such as the exec-only `--wait`;
`--memory-mib` takes the sandbox's default for a restore. The restore
links the snapshot's files into `--state-dir`, which must therefore be
on the store's filesystem, and outside the store, which SL3.8a
requires of every restored sandbox, at its provision, start, and
deprovision. Since the run removes it, it must not exist, and an empty
path, which names the working directory, is refused: a sandbox that it
names is never removed. A cold boot refuses `--store-dir`,
`--state-dir`, and the restore inputs, and a restore refuses the
options of a cold boot that the snapshot or its sandbox fixes:
`--guest`, `--memory-capacity-mib`, `--processors`, `--cpu-profile`,
`--net`, the share options, `--outcome-report`, `--cmdline`, and
`--dry-run`.

A raw OpenVMM snapshot is refused before anything is read or launched:
`--restore-snapshot` without `--store-dir`, or with a value that is no
snapshot name, such as a path, fails with a message that says that the
option names a snapshot of the store that `snapshot capture` captured.
`--restore-processors`, `--restore-memory-mib`, `--restore-ready-path`,
and `--restore-network-policy` only served the raw restore, and go: the
named restore takes its processors from the snapshot's key and its
memory from `--memory-mib`, owns its restore-ready endpoint, and always
installs the restore's own network policy.

A restored workload that exits with another status than 0 still fails
the start, which ends its VM and launch, but the start that `run` asks
for ends with that status, which `run` exits with: `snapshot restore`,
and each later `sandbox start` of a restored sandbox, keep their error,
"the restored workload ended with Exit status N", and status 1.

The snapshot fixes the NIC. The restore of a snapshot with a NIC takes
the NIC's address, and lowers the run's DNS server, egress, forwards,
and published listeners, which need `--network-profile`, into the
restore's own policy, as `sandbox` lowers them; that of a snapshot
without one refuses network options before it provisions anything.
`snapshot capture` gives its VM no NIC today, so only fake-backed tests
cover the policy that reaches the guest of a networked restore.

A `run` that dies before it removes its sandbox, as `kill -9` ends it,
leaves `--state-dir` and its VM, which keeps running until a command of
the sandbox ends it, and the snapshot's lock goes with the `run`. While
the workload runs, the start is unfinished, and `sandbox deprovision`
ends the VM, through the recovery of the start's launch journal, and
removes the directory; once the workload has exited with status 0,
`sandbox stop` must end the VM first, as it must after Ctrl-C while the
run stops the VM. usage.md says so.

The raw restore's code goes with it: the restore body and console
reattachment of `run`, the OpenVMM arguments of the network options,
and the reader of a snapshot manifest's attachments that only it used.
The `run-console-restore` microVM scenario, which restored a raw
snapshot with its boot console on the terminal, is retired with its
shard entries.

esaurez's partial implementation on esaurez/private-sl38b-0325-b2
(ef52ba6) restored blockless snapshots through unmerged
prerequisites. This keeps its refusal of raw snapshots and of the
options that a restore fixes, and restores through the layered
sandbox path of SL3.8a instead.

Evidence, on nvx-kvm-test (KVM, Azure Linux 3, kernel 6.6) and
nvx-mshv-test (MSHV, kernel 6.18), virtual machines, and on WHP on a
Windows 11 developer machine, with OpenVMM 14dc4116, the kernel of
release v0.1.0-dev.53ad09678bc1, and this tree's Alpine initramfs:
`snapshot capture` published `base`, and `run --restore-snapshot base`
restored the release's Ubuntu EROFS layer and a GPT image of its tree,
over a RAM upper and over a fresh scratch (GPT over a scratch on KVM
and MSHV), as nobody and as daemon (`--workload-user 1:1`), forwarded
the workload's output and its statuses 0, 7, and 1, and left no
directory, VM, or link to the snapshot's files behind, while `snapshot
restore` of /usr/bin/false still failed with its error and status 1.
The raw snapshot directory, with and without `--store-dir`, network
options for the snapshot without a NIC, a missing, existing, or
in-store `--state-dir`, and `--wait` were refused before launch. On
KVM and MSHV also: `--memory-mib 384` of a snapshot of 512 MiB capacity
(MemTotal 372564 kB, against 241492 kB), three concurrent one-offs of
one snapshot, SIGINT (status 130, the directory removed), and a state
directory of 89 bytes, whose socket fails the VM's creation and which
the run removes. A `run` that kill -9, or Stop-Process -Force on
Windows, ended while its workload ran left its VM running 120 s later,
while another restore of the snapshot succeeded; `sandbox deprovision`
alone ended that VM and removed the directory, as `sandbox stop`, which
then reports sandbox-not-running, and `deprovision` also did. A
restored sandbox whose workload had exited with status 0 refused
`deprovision` until `sandbox stop` ended it.

Tests: unit tests parse the restore options of `run` as those of
`snapshot restore`, give the restore of a snapshot the snapshot's NIC
with the policy of the run's options, and refuse network options for a
snapshot without a NIC; fake-backed restores install the run's network
options as the restore's own policy, with the DNS server and published
listeners of the activation record, and OpenVMM's egress, forward, and
host-loopback options without `--net`, refuse network options for a
snapshot without a NIC before they provision anything, and end with the
status 3 of their workload, once the VM and its launch have ended,
where `snapshot restore` keeps its error. CLI tests cover the raw
refusals, a missing store, the state directory's refusals, the options
that each kind of run refuses, the removed options and `--wait`, and
the help. The dry runs and launches of raw restores, the proptest of a
restore's policy paths, and the tests of the attachment reader are
retired with the code that they tested.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With SL3.4b's managed `sandbox run` and SL3.8b's named restore of `run`
both applied, nothing calls the one-shot foreground launch any more:
`host::cli::sandbox::one_shot` (`run_openvmm`), and on Windows
`platform::spawn_foreground` and `standard_handle`, whose dead code
fails the warning-denied Clippy build. SL3.8b's own rebase onto
feature/nvx-refactor deletes them; this commit stands in for that
deletion in the temporary local base of SL3.10b-d, and goes once
SL3.8b's branch holds it. It also drops the Windows test of
`spawn_foreground` and that function's line in the batch-file test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…and plan

Two of the shared sandbox modules still reached old APIs for values
that the new interface already owns. `sandbox/mod.rs` named the
artifacts' runtime identity through `host::store`, which only re-exports
`host::runtime::RuntimeIdentity`, and `sandbox/options.rs` found the
gateway of `--net` through `host::sandbox::plan::guest_gateway`, the old
plan's wrapper around `net::guest_gateway`.

`sandbox/mod.rs` now imports `RuntimeIdentity` from `host::runtime`.
`sandbox_network` checks `--net` with `Ipv4Cidr::parse`, whose refusal
is the plan's word for word, `guest network "ADDRESS" must be an IPv4
address with a /1 to /30 prefix that is not its network's network,
broadcast, or gateway (first) address`, with the same code, and takes
the gateway from `net::guest_gateway`, which finds one for every address
that parses. The order of the refusals does not change, and
`SandboxNetwork::new` still checks the network.

This is the first step of SL3.10b, whose split was approved once its
measured scope came to about ten times its estimate: SL3.10c moves the
sandbox network to `NetworkConfig`, and SL3.10d moves the launch off
`SandboxLaunch`, `SandboxSettings` and `SandboxUpper`.

Tests: `network_policies_that_the_planner_refuses_fail_before_launch`
in tests/cli.rs keeps checking, word for word, that `sandbox run
--dry-run` and `sandbox provision` refuse `--net 10.0.0.1/24` before
they launch or record anything.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The sandbox's network was `host::sandbox::SandboxNetwork`, one of the
old config types: `sandbox_network` returned it, the launch carried it,
and `config.json` stored it in the serde form of the old plan's network,
from which `start` read it back. `nvx-rs run` and `start` converted it
into the `NetworkConfig` of `host::config` that their VMs take.

`sandbox_network` now returns that `NetworkConfig`, after the checks
that `SandboxNetwork::new` made: the guest network, and that OpenVMM's
options can express the policy, with `host::network::openvmm_arguments`.
The configuration records the CLI's own network options in place of the
policy, as a `NetworkRecord`: `--net`, `--network-egress`,
`--network-ingress`, `--host-loopback`, `--network-proxy` and the
`--host-loopback-forward` options as given, and the egress rules in
OpenVMM's form, as the rule options gave them or a policy file compiled
them, so that no start reads that file again. Each start, and the
configuration that `sandbox run` writes and reads back, rebuilds the
options with the parser's own receiver and builds the network from them
with `sandbox_network`, as provision did. `provision` and `sandbox run`
pass the record in `ProvisionConfig`, beside the other options that
provision records. The checks of the network, their order and their
messages do not change. The record requires every field, an option
that was not given as `null`, and `Config::new` builds the network as
it reads the record, so a record whose options the parser or
`sandbox_network` refuses fails as `sandbox configuration is
malformed` when read, as the old plan's network did.

The record changes form, so the configuration moves to format 10.
Nothing in this workstream is deployed, so format 9 is refused rather
than migrated, as format 8 was, and the refusal of an unsupported format
now also says to deprovision the sandbox and provision it again.

This is the second step of SL3.10b, whose split was approved once its
measured scope came to about ten times its estimate: SL3.10d moves the
launch off `SandboxLaunch`, `SandboxSettings` and `SandboxUpper`.

Tests: the CLI's lifecycle cases of a network with a policy file, host
loopback and forwards, and of a proxy-only network, check the new
record, and their starts still launch OpenVMM with the same network
options; `old_configuration_formats_are_refused_without_migration`
refuses a format-9 configuration with the advice, which it lacked
before; the format checks of `malformed_configuration`,
`configurations_of_every_format_start_and_exec` and the
`sandbox-ram-upper` scenario take format 10, as does the fake `nvx-rs`
of that scenario's tests, whose old format is now 9;
`malformed_configuration` also refuses network records without a
field, of a gateway address, with another egress or with a broken rule
as malformed, all of which the record would otherwise read or refuse
later; and the restore test of `run`'s network options takes the
`NetworkConfig` directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna Pedro Henrique Penna (ppenna) added tests-modified Changes existing tests, test data, or test wiring feat-nvx-refactor labels Nov 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request changes existing tests

It modifies or removes 71 existing test asset(s), listed below. Review each with care: a change to a test can hide a regression that the test caught. See doc/contribute.md#changes-to-existing-tests.

Test code

Change What Before After
modified attributes the inner attributes of host::cli::sandbox::lifecycle::start::tests (it now compiles where any ( target_os = "linux" , windows ) and feature = "cli" and feature = "host" and test hold, not where feature = "cli" and feature = "host" and test did) nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:1 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:1
modified helper fn archive in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:36 nvx-rs/src/host/cli/download/archive/tests.rs:42
modified helper fn options in host::cli::snapshots::capture::tests nvx-rs/src/host/cli/snapshots/capture/tests.rs:126 nvx-rs/src/host/cli/snapshots/capture/tests.rs:126
modified helper fn attachment in host::snapshot::manifest::tests (moved, and it now compiles where feature = "host" and test hold, not where feature = "cli" and feature = "host" and test did) nvx-rs/src/host/snapshot/manifest/tests.rs:886 nvx-rs/src/host/snapshot/manifest/tests.rs:884
modified helper fn spawn_locked in host::tests (moved, and it now compiles where any ( target_os = "linux" , windows , feature = "cli" ) and feature = "host" and test hold, not where any ( target_os = "linux" , windows ) and feature = "host" and test did) nvx-rs/src/host/tests.rs:36 nvx-rs/src/host/tests.rs:36
modified helper fn dry_run in cli/dry_run (tests) nvx-rs/tests/cli/dry_run.rs:52 nvx-rs/tests/cli/dry_run.rs:47
modified helper fn run_dry_runs in cli/dry_run (tests) nvx-rs/tests/cli/dry_run.rs:346 nvx-rs/tests/cli/dry_run.rs:335
modified helper fn minimal_config in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:76 nvx-rs/tests/cli/lifecycle.rs:76
modified helper fn configurations in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:262 nvx-rs/tests/cli/lifecycle.rs:262
modified helper fn malformed_configuration in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:1226 nvx-rs/tests/cli/lifecycle.rs:1212
modified helper fn fails_a_cold_boot in cli/one_shot (tests) nvx-rs/tests/cli/one_shot.rs:128 nvx-rs/tests/cli/one_shot.rs:127
modified helper fn run_launches in cli/one_shot (tests) nvx-rs/tests/cli/one_shot.rs:149 nvx-rs/tests/cli/one_shot.rs:144
modified helper const USAGE_ERRORS in cli/usage (tests) nvx-rs/tests/cli/usage.rs:20 nvx-rs/tests/cli/usage.rs:20
modified helper const HELP in cli/usage (tests) nvx-rs/tests/cli/usage.rs:675 nvx-rs/tests/cli/usage.rs:726
modified helper fn run_refusals in cli/validation (tests) nvx-rs/tests/cli/validation.rs:744 nvx-rs/tests/cli/validation.rs:744
modified helper def _FakeRamUpperSandboxCli._provision in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:2744 scripts/test_microvm_tests.py:2744
modified imports the imports of host::cli::sandbox::lifecycle::start::tests (it now compiles where any ( target_os = "linux" , windows ) and feature = "cli" and feature = "host" and test hold, not where feature = "cli" and feature = "host" and test did) nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:3 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:3
modified imports the imports of cli/dry_run (tests) nvx-rs/tests/cli/dry_run.rs:5 nvx-rs/tests/cli/dry_run.rs:5
modified imports the imports of cli/one_shot (tests) nvx-rs/tests/cli/one_shot.rs:6 nvx-rs/tests/cli/one_shot.rs:6
modified imports the imports of cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:7 nvx-rs/tests/cli/openvmm.rs:6
modified test fn gzipped_packages_extract_once_their_names_and_checksums_pass in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:67 nvx-rs/src/host/cli/download/archive/tests.rs:74
modified test fn zip_packages_extract_with_the_tar_of_windows in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:82 nvx-rs/src/host/cli/download/archive/tests.rs:90
modified test fn packages_whose_checksums_fail_are_refused in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:93 nvx-rs/src/host/cli/download/archive/tests.rs:102
modified test fn packages_with_symlinks_are_refused in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:122 nvx-rs/src/host/cli/download/archive/tests.rs:132
modified test fn archives_that_tar_cannot_read_are_refused in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:135 nvx-rs/src/host/cli/download/archive/tests.rs:146
modified test fn queries_and_downloads_carry_the_token_to_the_origin_of_the_api_alone in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:136 nvx-rs/src/host/cli/download/github/tests.rs:142
modified test fn requests_without_a_token_carry_none in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:255 nvx-rs/src/host/cli/download/github/tests.rs:262
modified test fn redirects_resolve_relative_locations_against_the_urls_of_their_requests in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:277 nvx-rs/src/host/cli/download/github/tests.rs:285
modified test fn the_last_response_to_a_retried_request_counts in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:331 nvx-rs/src/host/cli/download/github/tests.rs:340
modified test fn run_checks_again_that_its_shares_do_not_overlap in host::cli::sandbox::lifecycle::start::tests (moved, and it now compiles where any ( target_os = "linux" , windows ) and feature = "cli" and feature = "host" and test hold, not where feature = "cli" and feature = "host" and test did) nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:13 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:843
modified test fn a_run_removes_its_vm_directory_with_a_report_s_staging_file in host::cli::sandbox::lifecycle::start::tests (moved, and it now compiles where any ( target_os = "linux" , windows ) and feature = "cli" and feature = "host" and test hold, not where feature = "cli" and feature = "host" and test did) nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:82 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:913
modified test fn restored_sandbox_records_round_trip_only_their_own_snapshot_name in host::cli::sandbox::state::tests (renamed to restored_sandbox_records_round_trip_only_their_own_source) nvx-rs/src/host/cli/sandbox/state/tests.rs:5 nvx-rs/src/host/cli/sandbox/state/tests.rs:7
modified test fn capture_parser_refuses_restore_bad_names_and_invalid_options in host::cli::snapshots::capture::tests (renamed to capture_parser_refuses_bad_names_and_invalid_options) nvx-rs/src/host/cli/snapshots/capture/tests.rs:251 nvx-rs/src/host/cli/snapshots/capture/tests.rs:250
modified test fn usages_are_one_line_and_helps_list_every_option in host::cli::tests nvx-rs/src/host/cli/tests.rs:14 nvx-rs/src/host/cli/tests.rs:14
modified test fn batch_files_are_refused_before_cmd_could_read_their_arguments in host::platform::windows::tests nvx-rs/src/host/platform/windows/tests.rs:443 nvx-rs/src/host/platform/windows/tests.rs:411
modified test fn the_attachments_that_openvmm_writes_are_read_in_their_order in host::snapshot::manifest::tests (moved, and it now compiles where feature = "host" and test hold, not where feature = "cli" and feature = "host" and test did) nvx-rs/src/host/snapshot/manifest/tests.rs:895 nvx-rs/src/host/snapshot/manifest/tests.rs:892
modified test fn run_guides_failed_cold_boots_on_cpus_that_no_built_in_profile_serves in cli (tests) nvx-rs/tests/cli.rs:2509 nvx-rs/tests/cli.rs:2509
modified test fn dry_runs_print_the_openvmm_command_and_launch_nothing in cli/dry_run (tests) nvx-rs/tests/cli/dry_run.rs:1271 nvx-rs/tests/cli/dry_run.rs:970
modified test fn configurations_of_every_format_start_and_exec in cli/formats (tests) nvx-rs/tests/cli/formats.rs:10 nvx-rs/tests/cli/formats.rs:10
modified test fn old_configuration_formats_are_refused_without_migration in cli/formats (tests) nvx-rs/tests/cli/formats.rs:100 nvx-rs/tests/cli/formats.rs:100
modified test fn one_shot_runs_exit_with_the_workload_or_openvmm_status in cli/one_shot (tests) nvx-rs/tests/cli/one_shot.rs:498 nvx-rs/tests/cli/one_shot.rs:331
removed helper fn terminal_console_snapshot in host::snapshot::manifest::tests nvx-rs/src/host/snapshot/manifest/tests.rs:916
removed helper fn with_snapshot in cli/dry_run (tests), impl DryRun`` nvx-rs/tests/cli/dry_run.rs:84
removed helper fn restore in cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:684
removed helper static TERMINAL_CONSOLE in cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:695
removed helper static DISCONNECTED_CONSOLE in cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:706
removed helper static NO_CONSOLE in cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:717
removed helper fn snapshot in cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:720
removed helper fn snapshot_manifest in cli/openvmm (tests) nvx-rs/tests/cli/openvmm.rs:733
removed helper class RunConsoleRestoreScenarioTests in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:4039

... and 12 more, which the job's summary lists.

End-to-end tests

Change What Before After
modified helper assignment MICROVM_TEST_SCENARIOS in scripts/nvx_tools/microvm_tests.py scripts/nvx_tools/microvm_tests.py:99 scripts/nvx_tools/microvm_tests.py:99
modified helper def _run_scenarios in scripts/nvx_tools/microvm_tests.py scripts/nvx_tools/microvm_tests.py:10894 scripts/nvx_tools/microvm_tests.py:10825
modified helper def RamUpperAcceptance.check_managed in scripts/nvx_tools/sandbox_ram_upper_tests.py scripts/nvx_tools/sandbox_ram_upper_tests.py:213 scripts/nvx_tools/sandbox_ram_upper_tests.py:213
removed file scripts/nvx_tools/microvm_test_scripts/run-console-restore.sh scripts/nvx_tools/microvm_test_scripts/run-console-restore.sh
removed helper assignment RUN_CONSOLE_RESTORE_MARKER in scripts/nvx_tools/microvm_tests.py scripts/nvx_tools/microvm_tests.py:397
removed helper def run_run_console_restore in scripts/nvx_tools/microvm_tests.py scripts/nvx_tools/microvm_tests.py:8695

CI test wiring

Change What Before After
removed scenario run-console-restore on kvm scripts/nvx_tools/microvm_shards.json
removed scenario run-console-restore on mshv scripts/nvx_tools/microvm_shards.json
removed scenario run-console-restore on whp scripts/nvx_tools/microvm_shards.json

Relocated unchanged: 0; added: 61. Compared the merge base d4edeeb9ab93 with d5fdcc648f04.

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

Deprovisioning a stopped format-9 sandbox with an existing restored openvmm/ view fails with format-unsupported before cleanup, blocking the recommended deprovision/reprovision recovery path.

1 open issue
What changed in this PR

This PR advances #1112 by replacing the sandbox CLI’s old network-plan type with NetworkConfig and CLI-owned network records. The supplied diff also includes stacked changes for named snapshot restores and removal of old supervisor dependencies.

Changes:

  • Record network options in format 10 and rebuild validated policies at each start.
  • Reject older configuration formats with reprovisioning advice, updating tests and documentation.
  • Include prerequisite snapshot-restore, process-handling, and obsolete raw-restore cleanup changes.
File Description
scripts/​test-layout-allowlist.json Remove exemptions for hooks now used in production.
scripts/​test_microvm_tests.py Update format fixtures and remove raw-restore tests.
scripts/​nvx_tools/​sandbox_ram_upper_tests.py Expect configuration format 10.
scripts/​nvx_tools/​microvm_tests.py Remove the raw console-restore scenario.
scripts/​nvx_tools/​microvm_test_scripts/​run-console-restore.sh Delete the obsolete scenario script.
scripts/​nvx_tools/​microvm_shards.json Remove the scenario and rebalance shards.
nvx-rs/​tests/​cli/​validation.rs Update refusals for named snapshot restores.
nvx-rs/​tests/​cli/​usage.rs Check restore options and removed flags.
nvx-rs/​tests/​cli/​openvmm.rs Remove raw snapshot test helpers.
nvx-rs/​tests/​cli/​one_shot.rs Remove obsolete raw-restore cases.
nvx-rs/​tests/​cli/​lifecycle.rs Check network records and malformed configurations.
nvx-rs/​tests/​cli/​formats.rs Check format 10 and older-format recovery advice.
nvx-rs/​tests/​cli/​dry_run.rs Remove unsupported restore dry-run cases.
nvx-rs/​tests/​cli/​download.rs Check restore refusals before downloads.
nvx-rs/​tests/​cli.rs Adapt restore-related diagnostic coverage.
nvx-rs/​src/​host/​vm/​tests.rs Add a prepared-launch substitution helper.
nvx-rs/​src/​host/​vm.rs Make owned-child access private.
nvx-rs/​src/​host/​tests.rs Add shared spawn-lock test helpers.
nvx-rs/​src/​host/​template/​mod.rs Remove the attachment-reader re-export.
nvx-rs/​src/​host/​snapshot/​manifest/​tests.rs Test attachments through the strict manifest reader.
nvx-rs/​src/​host/​snapshot/​manifest.rs Remove the raw-restore attachment reader.
nvx-rs/​src/​host/​restore_driver.rs Remove the unused-code allowance.
nvx-rs/​src/​host/​platform/​windows/​tests.rs Remove tests for the deleted foreground launcher.
nvx-rs/​src/​host/​platform/​windows/​spawn.rs Delete obsolete foreground-spawn helpers.
nvx-rs/​src/​host/​platform/​windows/​mod.rs Remove the foreground-spawn re-export.
nvx-rs/​src/​host/​cli/​tests.rs Validate the composed run-option list.
nvx-rs/​src/​host/​cli/​snapshots/​tests.rs Cover restore parsing and locked view cleanup.
nvx-rs/​src/​host/​cli/​snapshots/​capture/​tests.rs Adapt tests to capture-only parsing.
nvx-rs/​src/​host/​cli/​snapshots/​capture.rs Delegate subcommand selection to the snapshot parser.
nvx-rs/​src/​host/​cli/​snapshots.rs Add restore dispatch and snapshot-view management.
nvx-rs/​src/​host/​cli/​session/​tests.rs Check connection waits against process exit.
nvx-rs/​src/​host/​cli/​session.rs Use verified processes instead of supervisor children.
nvx-rs/​src/​host/​cli/​sandbox/​state/​tests.rs Check restored-source record round trips.
nvx-rs/​src/​host/​cli/​sandbox/​state.rs Introduce format-10 network records and validation.
nvx-rs/​src/​host/​cli/​sandbox/​options.rs Build and validate NetworkConfig directly.
nvx-rs/​src/​host/​cli/​sandbox/​one_shot.rs Delete the obsolete one-shot launcher.
nvx-rs/​src/​host/​cli/​sandbox/​mod.rs Pass network records and coordinate restores.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​stop.rs Remove restored views under snapshot locks.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start.rs Drive restores and carry network records into runs.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​mod.rs Carry network and source metadata through provision.
nvx-rs/​src/​host/​cli/​run/​tests.rs Cover shared restore parsing and snapshot NIC policy.
nvx-rs/​src/​host/​cli/​network.rs Remove direct OpenVMM argument emission.
nvx-rs/​src/​host/​cli/​mod.rs Dispatch snapshot capture and restore commands.
nvx-rs/​src/​host/​cli/​download/​github/​tests.rs Guard concurrent spawns and check missing-curl errors.
nvx-rs/​src/​host/​cli/​download/​github.rs Spawn curl directly.
nvx-rs/​src/​host/​cli/​download/​git.rs Spawn Git directly.
nvx-rs/​src/​host/​cli/​download/​archive/​tests.rs Protect archive-test spawns with the shared lock.
nvx-rs/​src/​host/​cli/​download/​archive.rs Spawn tar directly.
nvx-rs/​README.md Document network records and named restore behavior.
doc/​run.md Update format references and direct OpenVMM restore examples.

Review effort: Balanced

Comment thread nvx-rs/src/host/cli/sandbox/state.rs Resolved

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat-nvx-refactor tests-modified Changes existing tests, test data, or test wiring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants