Skip to content

nvx-rs: apply a clone's Activate to its root and workload - #940

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
esaurez/843-pr2-root-workload-activation-20261024
Oct 28, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
esaurez/843-pr2-root-workload-activation-20261024

Conversation

@esaurez

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

Copy link
Copy Markdown
Contributor

Stacked on #939; this PR retargets when its parent merges.
Chain: #933 → #880 → #936 → #937 → #939 → this PR → #941, on dev; #879, #881, #885, #886, #877, #932, #884, and #887 have merged.

Part of #843 (PR 2 of 3: root and workload activation).

What and why

#939's Activation hands the adapter the record of the one Activate that it accepted, and opens the fence of workload admission only once the adapter reports that the record was applied. This adds that application, nvx::guest::clone::apply(record, &RootTemplate) -> Result<Applied, ApplyError>, which activates the clone's root and workload as at a cold boot:

  • The root: nvx::guest::root::assemble, with the record's settings: its image, an EROFS image whose UUID it checks if the record expects one; its upper, a tmpfs or the scratch, with the record's journal policy; and its overlay mode. The assembly's other inputs come from a RootTemplate, which the guest takes from its own configuration, as its template captured it: the runtime directory, the distro and scratch block devices, and how long the assembly waits for them.
  • A GPT disk is refused before any side effect. The kernel created the block devices of a disk's partitions when it found the template's placeholder, so they need not be the clone's. nvx-rs: activate a clone's root for GPT images and either upper, with its export, network settings and workload view #845 adds GPT, with the rest of root activation.
  • The workload: with the record's workload identity, nvx::guest::entry::Identity::new(uid, gid), without supplementary groups or capabilities, in existing-account mode. Its account is checked in the assembled root before Ready, as NVX's init checks it at boot, and every execution runs as it. Without one, the adapter gives each execution its own identity, which Entry::check checks.
  • The result: the Applied root and identity, which the adapter keeps, or an ApplyError (Gpt, Assembly, or Account), after which nothing that it mounted stays mounted, unless the error holds the unmounts that failed. The adapter passes the result to Activation::complete. So a failed application is final, nothing retries it, and the fence still opens only inside complete.
  • It applies none of the record's other settings: its export, network, hostname, and machine ID, which nvx-rs: activate a clone's root for GPT images and either upper, with its export, network settings and workload view #845 adds. It starts no process and generates no ID.

Ready's activation fields come from nvx::activation::ReadyActivation, beside the adapter's own identities: that the activation applied, with the record's transaction ID. The adapter's framing comes in #941.

Later in the stack

#941 gives apply the activation's deadline: apply(record, template, deadline). The assembly waits for each device as long as the template's timeout allows, but never past the deadline: it checks the time before each poll, and sleeps no longer than until the deadline, at which it polls a last time if the deadline comes first, so that the application ends by then, past which Activation::complete fails the activation as expired.

Validation

This PR was rebased onto #937's new head, 5683164, without conflicts and with its patch unchanged. At its new head, 2747742, with the OpenVMM submodule at its gitlink, d2338ea2:

Linux x86_64:

  • nvx-rs: cargo fmt --check; Clippy with -D warnings, --all-targets --all-features, --lib, and --lib --no-default-features; cargo test --all-features (1798 passed), cargo test (877 passed), and the --lib --no-default-features build and test (260 passed); tests/host_modules.rs (22 passed); the bundled-artifacts test (19 passed); rustdoc with -D warnings; the MSRV (1.89) check; the aarch64-apple-darwin check.
  • Guest binaries: nvx.py check-guest-binaries (musl Clippy and tests, 980 passed), build-guest-binaries, test-guest-binaries (33 passed), check-static-elf.
  • tests/runtime-adapter, which builds on nvx-rs: fmt, Clippy, build, tests, and MSRV in its protocol-only (46 passed), host-only (98 passed), and musl guest (120 passed) configurations.
  • scripts/check_markdown_links.py.
  • As root, with standard input from /dev/null: the privileged capture test, the strict cold boot that stops at its violation, and the tests of guest::clone and guest::reseed (74 passed); the runtime adapter's library and guest-session tests (112 passed).

Windows (x86_64-pc-windows-msvc): nvx-rs fmt, Clippy (all features, --lib, no default features, each feature alone, and all features for the musl target), tests (all features 1118 passed, default 896, no default features 260), tests/host_modules.rs, rustdoc, MSRV; tests/runtime-adapter fmt, Clippy, build, tests (protocol-only 46 passed, host 99), and MSRV in its protocol-only and host configurations, and Clippy and MSRV in its musl guest configuration; the Markdown links. The adapter's musl rustdoc with -D warnings, which CI doesn't run, fails with the same five broken links in exec.rs and worker/mapping.rs as at #937's head; this PR doesn't touch them.

Per commit, on Linux x86_64 and on Windows: fmt, Clippy with -D warnings in CI's three configurations and --lib, and tests/host_modules.rs; on Linux, also the aarch64-apple-darwin check, and on Windows, Clippy for the musl target.

This PR needs no VM. #941 runs the runtime adapter's VM suites at the head of the stack, whose guest runs this PR's code: in the CI run below, at ea69821, 58 of 58 passed on KVM, on MSHV, and on WHP.

CI: ci.yml run 40619378275, dispatched on #941's head, ea69821, which covers this PR's code, since this chain's PRs don't target dev and get no PR CI, passed: 36 jobs succeeded, among them nvx-rs and the CLI on Linux and Windows, the guest artifacts, OpenVMM's binaries, unit tests, and vmm-tests, and the NVX microVM tests, with the runtime adapter's VM suites, and the platform jobs on KVM, MSHV, and WHP; the 6 jobs that only pull requests or dev run were skipped.

Tests added (nvx-rs/src/guest/clone/activation/apply/tests.rs). They drive the activation through an adapter of their own, whose fake system records every mount, account read, and teardown, and which spawns an execution only once the fence is open:

  • No mount, spawn, or ID generation happens before the gate.
  • A valid Activate activates the root and the workload once, before Ready; a later session, and a retry in it, get the established activation without applying the record again.
  • The assembly's and the identity's inputs are taken exactly from the record.
  • A workload identity without an account in the image is refused before Ready.
  • A failed assembly is final, and reads and tears down nothing; the application refuses, without privileges, what it cannot assemble; application errors name their cause.

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

No active review issues are reported for the root and workload activation changes.

No open issues 🎉

What changed in this PR

Implements root and workload activation for restored clones, building on #939’s activation state machine as part 2 of #843. The new API applies an accepted record before the adapter reports completion and opens workload admission.

Changes:

  • Adds apply, RootTemplate, Applied, and ApplyError for EROFS root assembly and workload-account validation, with cleanup on failure.
  • Adds fake-system tests covering admission gating, record inputs, retries, account refusals, and assembly failures.
  • Documents adapter integration and the settings deferred to #845, including GPT support, export, network, hostname, and machine ID.
File Description
nvx-rs/​src/​guest/​mod.rs Updates the guest-module overview with root and workload activation.
nvx-rs/​src/​guest/​clone/​activation/​apply/​tests.rs Tests activation ordering, settings, retries, and failure cleanup.
nvx-rs/​src/​guest/​clone/​activation/​apply.rs Implements root assembly, identity validation, and application results.
nvx-rs/​src/​guest/​clone/​activation.rs Exports the application API and updates the adapter example.
nvx-rs/​src/​guest/​clone.rs Re-exports the API and documents its lifecycle and privilege requirements.
nvx-rs/​README.md Documents clone application behavior and its current scope.

Review effort: Balanced

A clone's activation hands the adapter the record of the one Activate
that it accepted. nvx::guest::clone::apply now applies it as at a cold
boot, and returns the result that the adapter reports to
Activation::complete:

- It assembles the root with nvx::guest::root::assemble, with the
  record's image, an EROFS image whose UUID it checks if the record
  expects one, its upper, a tmpfs or the scratch with the record's
  journal policy, and its overlay mode, and with a RootTemplate: the
  inputs that the guest takes from its own configuration, which its
  template captured, the runtime directory, the distro and scratch
  devices, and how long the assembly waits for them.
- It refuses a GPT disk before anything has a side effect, since the
  kernel created the block devices of the disk's partitions when it
  found the template's placeholder.
- With the record's workload identity, it checks the identity's account
  in the assembled root before Ready, as NVX's init checks it at boot:
  Identity::new(uid, gid), without supplementary groups or
  capabilities, in existing-account mode, in which every execution then
  runs. Without one, each execution names its own identity, which
  Entry::check checks.

It returns the Applied root and identity, which the adapter keeps, or an
ApplyError, after which nothing that it mounted stays mounted, unless
the error holds the unmounts that failed. It applies none of the
record's other settings, its export, network, hostname, and machine ID,
and it starts no process and generates no ID.

Its tests drive the activation through an adapter of their own, whose
fake system records every mount, account read, and teardown, and which
spawns an execution only once the fence is open: nothing happens before
a valid Activate, the assembly's and the identity's inputs come exactly
from the record, and an image without the workload identity's account
is refused before Ready.

Part of #843

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a2f342b-b46d-4a26-a11e-a996e3c1e925
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/843-pr1-activation-state-machine-20261024 branch from 4e08935 to 62d8f3a Compare October 28, 2026 02:09
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/843-pr2-root-workload-activation-20261024 branch from d3809fb to 2747742 Compare October 28, 2026 02:09
Base automatically changed from esaurez/843-pr1-activation-state-machine-20261024 to dev October 28, 2026 18:28
@ppenna
Pedro Henrique Penna (ppenna) merged commit 86ae0e7 into dev Oct 28, 2026
44 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the esaurez/843-pr2-root-workload-activation-20261024 branch October 28, 2026 19:05
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