Repository navigation
cli: let a named snapshot carry a NIC (SL3.7c) - #1502
Merged
Pedro Henrique Penna (ppenna) merged 2 commits intoNov 12, 2026
Merged
Pedro Henrique Penna (ppenna) merged 2 commits into
Pedro Henrique Penna (ppenna) merged 2 commits into
Conversation
Contributor
This pull request changes existing testsIt modifies or removes 30 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
End-to-end tests
CI test wiring
Relocated unchanged: 0; added: 19. Compared the merge base |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The port-denial tests can falsely pass if delayed marker delivery lets the guest listener expire before probing, since the timing guard starts only when the host consumes the marker.
1 open issue
What changed in this PR
This PR completes SL3.7c of #1114 by letting named snapshots carry a network interface, enabling the existing restore commands to apply their own network policies to real guests.
Changes:
- Adds opt-in
snapshot capture --netwith a deny-all capture policy. - Makes
snapshot restoreuse the captured address, reject--net, and validate network options before provisioning. - Rewrites network-policy acceptance around named snapshots and moves it into each backend’s public sandbox CI suite.
| File | Description |
|---|---|
scripts/test_nvx_tools.py |
Checks the scenario’s public acceptance CI wiring. |
scripts/test_microvm_tests.py |
Tests CLI orchestration, guest checks, refusals, and cleanup. |
scripts/nvx_tools/microvm_tests.py |
Runs four network policies through named-snapshot CLI restores. |
scripts/nvx_tools/microvm_test_scripts/untiered-network-policy.sh.in |
Adapts guest probes and listener handling for restored workloads. |
scripts/nvx_tools/microvm_shards.json |
Removes the scenario from default shards and rebalances them. |
nvx-rs/src/host/cli/snapshots/tests.rs |
Tests network validation before provisioning. |
nvx-rs/src/host/cli/snapshots/capture/tests.rs |
Tests address parsing, deny-all capture, and retained NIC metadata. |
nvx-rs/src/host/cli/snapshots/capture.rs |
Adds optional NIC configuration to capture. |
nvx-rs/src/host/cli/snapshots.rs |
Uses the captured NIC and rejects restore-time addressing. |
nvx-rs/src/host/cli/run.rs |
Updates rustdoc for shared restore-network handling. |
nvx-rs/README.md |
Documents networked capture and restore behavior. |
doc/usage.md |
Documents CLI options, restrictions, and scenario requirements. |
doc/run.md |
Clarifies named-snapshot network-policy replacement. |
doc/ci.md |
Describes the rewritten acceptance scenario and its CI placement. |
doc/acceptance-case-matrix.md |
Attributes named-snapshot network coverage to SL3.7c. |
.github/workflows/run-nvx-microvm-tests.yml |
Adds the scenario to Linux and Windows public acceptance steps. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl3-8b-run-restore
branch
from
November 12, 2026 00:20
a69c751 to
ce25a7f
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl3-7c-snapshot-nic
branch
from
November 12, 2026 00:37
12358d6 to
ee8c9e1
Compare
Base automatically changed from
agents/slim-sl3-8b-run-restore
to
feature/nvx-refactor
November 12, 2026 00:37
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl3-7c-snapshot-nic
branch
from
November 12, 2026 00:52
ee8c9e1 to
d2b0aa0
Compare
`snapshot capture` gave its VM no NIC, and a snapshot restores only with the NIC presence that it captured, so no named snapshot could restore with a network: the restore's own DNS server, egress, forwards, and published listeners, which `snapshot restore` (SL3.8a) and `run --restore-snapshot` (SL3.8b) install, reached no real guest. `snapshot capture --net IPV4/PREFIX` now gives the capture VM a NIC at that static address under the complete deny-all policy, with egress, ingress, and host-loopback access denied and no rules, forwards, proxy, or DNS server, the one policy whose NIC a restore may give a policy of its own. The library already attaches a capture's NIC under that policy and records the NIC in the capture context, the key, and `snapshot.json`, so `Snapshot::nic` returns it. Without `--net`, the capture is unchanged. `snapshot restore` now takes its network at the snapshot's NIC, as `run --restore-snapshot` does: both take the NIC that `snapshots::restore_source` returns, and the network options of the restore through `run::restored_network`. The network options of a snapshot with a NIC, which need `--network-profile`, become the restore's own policy at the NIC's address, through the same lowering, and those of a snapshot without a NIC are refused, before anything is provisioned, where the start's `Snapshot::accepts` refused them after the provision. `--net`, which only a cold boot takes, joins the options that `snapshot restore` leaves unrecognized: a restore keeps the address that its snapshot captured. The help of `snapshot restore`, `snapshot capture` and `run`, and the docs, say that a restore's NIC comes from its snapshot, and that `snapshot capture --net` gives a snapshot its NIC. Not here: re-addressing a restored VM's NIC, and snapshots of the running workloads of blockless VMs. Tests: the capture parser takes for `--net` only an address that OpenVMM accepts; a capture with `--net` attaches its VM's NIC under the complete deny-all policy alone, and its snapshot keeps the NIC and its address, in its key and description, while one without has no NIC; and `snapshot restore` refuses the network options of a snapshot without a NIC and a snapshot with a NIC without `--network-profile` before it provisions anything, and gives the options of one with a NIC the NIC's address. The fake VMM of the capture tests writes a manifest with a NIC for a VM that has one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The untiered-network-policy scenario drove OpenVMM directly: it booted a VM with a NIC under the complete deny-all policy, captured a raw snapshot of it with a post-restore script, and restored that snapshot with `--restore-network-policy` and four network policies. The CLI no longer restores raw snapshots, and `snapshot capture --net` now captures a named snapshot with such a NIC, so the scenario now runs on named snapshots, through the CLI's restore paths. It captures the template with `nvx-rs snapshot capture --net` and restores it four times, each into a sandbox of the Ubuntu layer over a RAM upper, with the network options of a policy at the NIC's captured address, and the scenario's checks as the restored workload: `snapshot restore`, whose sandboxes the scenario then stops and deprovisions, restores the template's own deny-all and private-network ingress with allowed egress, which show the restore's DNS server and announcer (SL3.8a's check); and `run --restore-snapshot` restores egress rules that admit DNS over IPv4 and one host service, and explicit host loopback with a forward under egress rules that admit DNS over IPv6 alone, which show its DNS server, egress, forwards, and published listeners (SL3.8b's checks). Each restored workload reports its resolver, whether the announcer of its listeners runs, and which host services it reaches, then serves the host through the forward and the published port, as many clients as the scenario names, or without one, holds its port for six seconds, while the host checks that nothing publishes it. The host cannot end the hold, as a restored workload takes no input and deny-all has no way in, so it must check within two seconds of seeing the server listen, and see the hold end at least two seconds after its check: a check that a late marker could have moved past the hold fails instead of passing. The workload's script is its `sh -c` argument, whitespace and all, as a restore sends its workload as an exec request. The scenario keeps its checks of those reports, of the requests that the host services receive, of where each policy publishes the guest's port, and that no restore changes the template, and adds that no restore leaves its sandbox or an OpenVMM process behind. Only the raw parts retire: the raw capture and restores, the guest's repair marker, the console release, and OpenVMM's refusals of a raw restore that keeps the template's policy but passes another or passes private-network ingress without `--restore-network-policy`, which the CLI cannot issue. In their place, the CLI must refuse, before it provisions anything, a restore of the template without `--network-profile`, and one that names the NIC's address with `--net`. A failed run stops the sandboxes that it left, ends the OpenVMM processes that name its fixture, keeps the VM log of each sandbox, and then deprovisions the sandboxes, as the sandbox lifecycle acceptance cleans up, and a failed restore's error names its cause. The scenario now reads the Ubuntu layer, so it becomes a public sandbox scenario: CI runs it in each backend's guest and sandbox job, beside the other public scenarios, and no longer in the shards of the default suite, which plan-microvm-shards re-planned. The case matrix now gives the scenario's rewrite, and that of the tests of the adapter's clones that it covers, to SL3.7c. Tests: the unit tests of the scenario run it against a simulated nvx-rs, whose restores' workloads request the real host services as their policies admit, and keep checking the reports, the host services, the publications, and the template, and now the commands of the capture, the restores, their stops and deprovisions, and the refusals, a script that fits one argument of an exec request, the timing of the host's check of an unpublished port against the guest's hold, also with markers that reach the host late, the cleanup after a failure, and the leftovers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl3-7c-snapshot-nic
branch
from
November 12, 2026 08:58
d2b0aa0 to
9a91a4f
Compare
Pedro Henrique Penna (ppenna)
marked this pull request as ready for review
November 12, 2026 08:58
Pedro Henrique Penna (ppenna)
merged commit Nov 12, 2026
eb05744
into
feature/nvx-refactor
84 of 87 checks passed
Pedro Henrique Penna (ppenna)
deleted the
agents/slim-sl3-7c-snapshot-nic
branch
November 12, 2026 09:11
This was referenced Nov 12, 2026
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 #1114 (SL3.7c, "a named snapshot can carry a NIC"). Parent #1084, umbrella #1076.
What and why
snapshot capturegave its VM no NIC, and a snapshot restores only with the NIC presence that it captured. So no named snapshot could restore with a network: the restore's own DNS server, egress, forwards and published listeners, whichsnapshot restore(SL3.8a) andrun --restore-snapshot(SL3.8b) install, never reached a real guest (gap analysis, slice definition).snapshot capture --net IPV4/PREFIX(opt-in) gives the capture VM a NIC at that static address, under the complete deny-all policy: no egress, ingress or host-loopback access, and no rules, forwards, proxy or DNS server. That is the one policy whose NIC a restore may replace with its own. The library already attaches a capture's NIC under that policy and records it in the capture context, the key andsnapshot.json(Snapshot::nic). Without--net, capture is unchanged.snapshot restoretakes its network at the snapshot's NIC, asrun --restore-snapshotdoes. Both commands take the NIC thatsnapshots::restore_sourcereturns (cli: restore managed sandboxes from named snapshots (SL3.8a2) #1487, cli: restore named snapshots once with run (SL3.8b) #1489), and apply their network options throughrun::restored_network, using the existing lowering. A NIC-less snapshot refuses network options before anything is provisioned;Snapshot::acceptsused to refuse them only after the provision.--net(approved by Pedro Henrique Penna (@ppenna)). The snapshot decides whether a restore has a NIC, and at which address. A restore of a NIC snapshot brings only its own DNS, egress, forwards and listeners. Sosnapshot restoretreats--netas a cold-boot-only option and leaves it unrecognized, andrun --restore-snapshotkeeps refusing it. The help ofsnapshot restore,snapshot captureandrun,doc/usage.mdandnvx-rs/README.mdsay that a restore's NIC comes from the snapshot, and that--netbelongs tosnapshot capture. This differs from esaurez's first implementation (esaurez/private-sl38b-0325-b2, used only as a reference), which kept--netonsnapshot restoreand required it to repeat the captured address.untiered-network-policynow runs on named snapshots through the public CLI, and only its raw parts retire.snapshot capture --net 192.0.2.2/24and restores it four times into sandboxes of the Ubuntu layer over a RAM upper:snapshot restore, thensandbox stopandsandbox deprovision;run --restore-snapshot.--network-profileand one with--net.sh -cargument, whitespace and all, since a restore sends its workload as an exec request (SL3.8a2).LISTENING, and see the hold end at least 2 s after its check; otherwise the check fails instead of passing (Copilot's review).snapshot-coreandsnapshot-tiers, and no longer in the default suite's shards (microvm_shards.jsonre-planned withplan-microvm-shards).rewritten, and names the raw parts that retire and the CLI's refusals that replace OpenVMM's. The MicroVM scenarios' summary row is recounted: of 54 tests, 50 kept, 3 rewritten and 1 retired (51, 2 and 1 before). The rows of the adapter's clone tests that the scenario covers now say what its restores check, and the Snapshots section lists the restore of a snapshot with a NIC, which installs a network policy of its own.Not in this PR: re-addressing a restored VM's NIC, and snapshots of blockless VMs' running workloads.
Size
Measured against
feature/nvx-refactorf03c21c3f0c, as #1076 defines them; the same as against the earlier base1b902ac16a5:--scenariolines:capture.rs15,snapshots.rs10, andrun.rs3, the lines of its--nethelp (estimate: about 80; stop at 160).nvx-rs/src/host/cli/snapshots/capture.rsandnvx-rs/src/host/cli/snapshots.rs;nvx-rs/src/host/cli/run.rs, for its help text only, so 4 files if help text counts as docs;scripts/nvx_tools/microvm_tests.py, the scenario. It is end-to-end test code (417 added lines), whose lines count as tests, as in tests: run every cold cell and the public VM lifecycle on real VMs (SL5.1b) #1496;.github/workflows/run-nvx-microvm-tests.yml, because the scenario moves to the public acceptance steps.Commits
32ef4e09a92cli: let a named snapshot carry a NIC (SL3.7c):snapshot capture --net;snapshot restore's network at the snapshot's NIC;--netleft unrecognized bysnapshot restore; unit tests, help, rustdoc and docs (doc/usage.md,doc/run.md,nvx-rs/README.md).9a91a4fe077tests: run untiered-network-policy on named snapshots (SL3.7c): the scenario rewrite, its unit tests, the CI wiring, the shard plan, and the docs (doc/ci.md,doc/usage.md,doc/acceptance-case-matrix.md).Validation
Hosts: KVM on
nvx-kvm-test(Azure Linux 3.0, kernel 6.6), MSHV onnvx-mshv-test(Azure Linux 3.0, kernel 6.18 mshv3), and WHP onnvx-whp-test. At earlier heads, whilenvx-whp-testwas degraded, WHP ran on the local Windows workstation. The guest artifacts come from CI run 43135776204 (feature/nvx-refactor012123bf9ac, an ancestor of the base; no guest code has changed since), and at earlier heads from run 43075697333. OpenVMM is at pin14dc4116, the same as this branch's: on Linux from releasev0.1.0-dev.53ad09678bc1, becausenvx-rs downloadfinds no release for this branch, and on WHP from CI run 43135776204.At the head
9a91a4fe077, after the rebase ontofeature/nvx-refactorf03c21c3f0c:Required status checkamong them, and 9 were skipped. The "Guests and sandboxes" jobs of KVM, MSHV and WHP ranuntiered-network-policyin "Run public managed sandbox acceptance", aftersnapshot-coreandsnapshot-tiers, whose named snapshots have no NIC, and all three passedcargo fmt --all --check;cargo clippy --lockedwith--all-targets --all-features,--lib,--lib --no-default-features,--lib --bins --features cli,--lib --bins --no-default-features --features testing, all-D warningscargo test --locked --all-features --lib -- host::cli::snapshots host::cli::run host::cli::sandbox host::snapshotcargo test --locked --all-features --test cli -- snapshot restore run captureRUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --all-features;python scripts/check_test_layout.pyruff check,ruff format --check, pyright (Linux and Windows) on the scenario and its tests;python -m unittest -k untiered scripts/test_microvm_tests.py;CiConfigurationTestsand the usage doc's test inscripts/test_nvx_tools.pycheck_matrix.py, against99d2022085f) and the docs' linkstest-microvm --scenario untiered-network-policy--backend whpnvx-whp-test): 2 runsresolver=none announcer=none allowed=denied other=denied, 0 exchanges; egress-dns:resolver=192.0.2.1 announcer=none allowed=reached other=denied, 0; host-loopback:resolver=fd00::c000:201 announcer=running allowed=denied other=denied, 2; private-ingress:resolver=192.0.2.1 announcer=running allowed=reached other=reached, 1; refusals:the snapshot has a NIC, so its restore needs --network-profileand--net is not accepted for restoresnapshot.json"nic": null), restored bysnapshot restore(then stopped and deprovisioned) and byrun --restore-snapshotwithout network optionslo, and no sandbox is left--network-profile portable, or--network-egress allow, through both commandsthe snapshot has no NIC, so its restore takes no network options--net 192.0.2.2/24 --network-profile portable, through both commandssnapshot restoreleaves--netunrecognized (status 2), andrun --restore-snapshotsays--net is not accepted for restore(status 1). No OpenVMM process was left on any hostAt earlier heads of this PR, whose code differs only as the commits above describe:
test-microvm --scenario untiered-network-policyee8c9e177e6)--backend whpd2b0aa0c1b3), and 4 beside 24 CPU-bound processesnvx-whp-testand CI's WHP job at this headcheck-nvx-rsthat runs on Windows (fmt, five clippy runs, tests with all, default and no default features, bundling, docs, MSRV 1.89, the runtime adapter's protocol-only and host-only configurations)public_api.py;cargo test --locked --all-features --no-fail-fastnvx-kvm-test)guest::unit tests that fail on that host at the base toocompileallpython -m unittest scripts/test_microvm_tests.pytest_private_network_ingress_script_checks_policy_and_traffic, whose 10 s budget for a Gitshscript ran out under load. That test and its script don't change here, and it passed 3 times aloneLeft to CI: the guest binaries and the macOS check (no guest or platform code changes here), and the shell linters (no listed script changes).
Test changes (
tests-modified)python3 scripts/check_test_changes.py --base origin/feature/nvx-refactorlists 30 changes to existing tests and 19 additions, compared withf03c21c3f0c:host::cli::snapshots::capture::tests::write_recipe: the fake VMM's manifest has a NIC when the capture's context has one, as OpenVMM's has for a VM with a NIC.GuestConsoleEchoTests.test_scripts_awaiting_a_release_turn_the_echo_off_first: dropsuntiered-network-policy.sh.in, which no longer awaits a console release, because a restored sandbox's workload has no console.test_untiered_network_policy_script_reports_the_repair_and_serves_the_host, renamed..._reports_the_policy_and_serves_the_host: the script takes the number of exchanges as its argument in place of the console release, holds its port when it has no client, and reports what the activation set up in place of the raw restore's repair.test_untiered_network_restores_name_what_their_policies_give_the_guest: also names which restores are one-offrun --restore-snapshots, without the raw restores'--restore-network-policysettings.test_untiered_network_policy_restores_one_template_with_four_policies, which the tool lists as removed, becametest_untiered_network_policy_restores_one_named_template_with_four_policies. It checks the CLI's commands in place of OpenVMM's: the capture with--net; the four restores with their policies and scripts; the stops and deprovisions; the refusals; and the host's wait for the end of each hold.test_untiered_network_policy_requires_what_each_policy_gives_the_guest: the same assertions, through the simulated CLI.test_untiered_network_policy_reports_guest_failuresandtest_untiered_network_policy_checks_where_ingress_publishes_the_guest_port: the same assertions; the failure message now also names its cause._simulate_untiered_network_guestand_run_untiered_network_policysimulate nvx-rs in place of OpenVMM.CiConfigurationTests.test_ci_runs_the_public_sandbox_acceptance_on_every_backend(test_nvx_tools.py): expects--scenario untiered-network-policyin the public acceptance steps.SANDBOX_CONTROL_SCENARIOSandPUBLIC_SANDBOX_SCENARIOS, and_run_scenariosgives it the backend, the timeout and the output directory, in place of OpenVMM's executable, kernel, initramfs and memory size. Only the raw parts retire:UNTIERED_POST_RESTORE_MARKER,UNTIERED_NETWORK_GUEST_MARKERS);UNTIERED_NETWORK_CHECKED,UNTIERED_NETWORK_MAX_CLIENTS) andUntieredNetworkRestore.network_settings;--restore-network-policy, which the CLI can't issue. The CLI's refusals of a restore without--network-profileand of one with--nettake their place.untiered-network-policyleaves the default suite's shards on kvm, mshv and whp, and runs in the public acceptance steps instead.the_capture_parser_takes_for_net_only_an_address_that_openvmm_accepts;a_capture_with_net_gives_its_vm_a_deny_all_nic_which_its_snapshot_keeps;a_restore_takes_its_network_at_the_nic_of_its_snapshot_before_it_provisions;test_untiered_network_policy_script_fits_one_argument_of_an_exec_request;test_untiered_network_exchanges_check_first_that_nothing_publishes_the_port;test_untiered_network_hold_must_cover_the_hosts_check;test_untiered_network_policy_fails_on_markers_that_reach_the_host_late;test_untiered_network_policy_stops_and_removes_what_a_failure_leaves;test_untiered_network_policy_leaves_no_openvmm_process_or_sandbox;test_untiered_network_policy_requires_the_clis_refusals;test_runner_dispatches_public_untiered_network_policy.