Repository navigation
nvx-rs: set a clone's hostname and machine ID for every execution - #959
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
Per-clone entropy uniqueness, execution mount inheritance, and teardown require verification of the VM scenarios isolated in #961 and KVM/MSHV/WHP results after the final restack.
No open issues 🎉
What changed in this PR
This PR implements the workload-view portion of #845: clone activation prepares the machine ID once, and every execution receives the record’s hostname. It also manages runtime binds through teardown, while leaving cold-boot behavior unchanged.
Changes:
- Generate and bind a clone’s machine ID after activation validation and account checks, reusing it across executions and retries.
- Preserve runtime binds with the activated root, remove them before teardown, and classify workload-view failures as status 55.
- Extend tests and documentation for hostname propagation, machine-ID modes, activation ordering, and cleanup. VM scenarios are tracked separately in #961.
| File | Description |
|---|---|
| tests/runtime-adapter/tests/guest_session.rs | Tests workload-view failures and execution hostnames. |
| tests/runtime-adapter/src/worker/executions.rs | Adds per-sandbox hostname configuration. |
| tests/runtime-adapter/src/worker/activation.rs | Propagates clone hostnames and maps application failure statuses. |
| tests/runtime-adapter/src/worker.rs | Retains activated roots and removes runtime binds during teardown. |
| tests/runtime-adapter/README.md | Documents clone workload views, cleanup, and failure statuses. |
| nvx-rs/src/guest/mappings.rs | Adds a test-only teardown-error constructor. |
| nvx-rs/src/guest/clone/activation/apply/tests.rs | Tests generation, binding, retries, failures, and teardown ordering. |
| nvx-rs/src/guest/clone/activation/apply.rs | Implements workload-view application and runtime-bind ownership. |
| nvx-rs/src/guest/clone/activation.rs | Exports the new teardown error and machine-ID filename constant. |
| nvx-rs/src/guest/clone.rs | Exposes and documents the expanded activation API. |
| nvx-rs/README.md | Updates the clone API overview with workload-view behavior. |
Review effort: Balanced
f3ebadd to
d5c55de
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Persistent runtime binds cross execution mount namespaces and alter privileged teardown ordering, requiring confirmation of the privileged test and VM scenarios on KVM, MSHV, and WHP that this review could not execute.
No open issues 🎉
Review effort: Balanced
d5c55de to
e6dd0ac
Compare
1c62dcf to
311deb2
Compare
311deb2 to
5102663
Compare
e6dd0ac to
ff1efdf
Compare
A clone's activation assembled its root and checked its workload identity, but left the record's hostname and machine ID unapplied. It now applies the workload's view before Ready: - If the record asks for a generated machine ID, nvx::guest::clone::apply generates one once, after the account check, with MachineId::generate, from the random number generator that the repair reseeded before the activation accepted the record, so that no two clones of a template share it. It writes it to the new MACHINE_ID_FILE, workload-machine-id, in the template's runtime directory, as the managed agent writes /run/nvx/workload-machine-id, and binds it read-only over the root's /etc/machine-id with Entry::bind_runtime, in the caller's mount namespace, of which each execution's starts as a copy. With the image's own machine ID, it binds nothing. A retry of the Activate never applies the record again, so every execution and every retry sees the same ID. - The entry that binds checks the record's hostname first, and Applied::hostname returns it, which the runtime adapter gives every execution's entry through Entry::hostname. - Applied now holds the RuntimeBinds: Applied::teardown removes them with RuntimeBinds::remove before Root::teardown, since they keep the root's mount busy, and reports both through the new AppliedTeardownError; dropping an Applied removes them before the root drops. into_parts returns them for a caller that keeps them apart. A generation or a bind that fails is ApplyError::MachineId or ApplyError::View, once the root is torn down. The runtime adapter maps each application failure to its class explicitly: the GPT reread and the assembly to 54, ACTIVATION_ROOT, and the account, the machine ID and the view to 55, ACTIVATION_WORKLOAD, leaving 56 and 57 to the export and the network. It keeps the Applied, whose teardown its stop runs. The cold path is unchanged: it generates and binds nothing. The tests cover, without a VM, that no ID is generated before the gate, and one only once after it, the hostname and both machine-ID modes, the binds' removal before the root's teardown, and each failure's status. An ignored test checks the generation and the read-only bind against the kernel, as root. The VM tests of concurrent clones, the image's own machine ID, and the failure statuses come with the restore of clones in VMs. Part of #845 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80932e09-af82-4b79-b417-70a241303cda
A clone's guest sets the hostname and the machine ID of the record's workload view for every execution: the image's own machine ID, or one that it generates once for the clone, which a read-only bind holds at /etc/machine-id. These VM tests check that view in clones that run at once. start takes --machine-id image|generated, the image's own by default, and clone::Request passes the mode to the clone's activation record. In vm_clone, one template serves three clones at once: over a scratch image and in RAM, each with a generated machine ID, and in RAM with the image's own. Two executions in each, as user ID 0, see the hostname, none of the markers that the other clones' executions wrote in their roots, and the clone's machine ID: a generated one, distinct between the clones, the same in both executions, held by one bind that is read-only, nosuid and nodev, which a write cannot change; or, with the image's own, none, since the image has none, and then the one that the first execution wrote. Each stop then tears its root down, binds included. A clone of a GPT disk whose layer holds a regular file etc starts with the image's own machine ID, which needs no bind, and ends with status 55 before it is ready with a generated one, which no bind can hold below a file. The template of that scenario is of the EROFS image, so it also restores a GPT clone of an EROFS placeholder. The scenarios' GPT disks may hold regular files in their top layer. Part of #845 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80932e09-af82-4b79-b417-70a241303cda
ff1efdf to
c5abb73
Compare
Part of #845 (PR 2 of 3: the workload view).
Stacked on #957: this PR targets its branch,
esaurez/845-pr1-gpt-fresh-scratch-20261024, and is rebased and retargeted with it.Chain: #951 → #954 → #957 → this PR → #960, on
dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, #880, #936, #937, #939, #940, #941, #888, #949, and #950 have merged.It builds on #876, merged: the resolver bind and the removable runtime binds.
What and why
A clone's activation assembled its root and checked its workload identity, but left the record's hostname and machine ID unapplied. It now applies the workload's view before Ready.
nvx-rs: set a clone's hostname and machine ID for every execution
nvx::guest::clone::applygenerates one once, after the account check, withMachineId::generate, from the random number generator that the repair reseeded before the activation accepted the record, so that no two clones of a template share it. It writes it to the newMACHINE_ID_FILE,workload-machine-id, in the template's runtime directory, as the managed agent writes/run/nvx/workload-machine-id, and binds it read-only over the root's/etc/machine-idwithEntry::bind_runtime, in the caller's mount namespace, of which each execution's starts as a copy. With the image's own machine ID, it binds nothing. A retry of the Activate never applies the record again, so every execution and every retry sees the same ID.Applied::hostnamereturns it, which the test adapter gives every execution's entry throughEntry::hostname.Appliednow holds theRuntimeBinds:Applied::teardownremoves them withRuntimeBinds::removebeforeRoot::teardown, since they keep the root's mount busy, and reports both through the newAppliedTeardownError; dropping anAppliedremoves them before the root drops.into_partsreturns them for a caller that keeps them apart.A generation or a bind that fails is
ApplyError::MachineIdorApplyError::View, once the root is torn down. The test adapter maps each application failure to its class explicitly: the GPT reread and the assembly to 54,ACTIVATION_ROOT, and the account, the machine ID and the view to 55,ACTIVATION_WORKLOAD, leaving 56 and 57 to the export and the network. It keeps theApplied, whose teardown its stop runs. The cold path is unchanged: it generates and binds nothing.runtime-adapter: start clones with generated machine IDs, in VMs
runtime-adapter-host starttakes--machine-id (image | generated), the image's own by default, andclone::Requestpasses the mode to the clone's activation record.In
vm_clone, one template serves three clones at once: over a scratch image and in RAM, each with a generated machine ID, and in RAM with the image's own. Two executions in each, as user ID 0, see the hostname, none of the markers that the other clones' executions wrote in their roots, and the clone's machine ID: a generated one, distinct between the clones, the same in both executions, held by one bind that is read-only,nosuidandnodev, which a write cannot change; or, with the image's own, none, since the image has none, and then the one that the first execution wrote. Each stop then tears its root down, binds included.A clone of a GPT disk whose layer holds a regular file
etcstarts with the image's own machine ID, which needs no bind, and ends before it is ready with a generated one, which no bind can hold below a file: status 8, by the evidence of invalid settings, with the guest's status 55. The template of that scenario is of the EROFS image, so it also restores a GPT clone of an EROFS placeholder.Not in this PR
Validation
Each commit of this PR, on #954's head 21b9983, with OpenVMM at 832d5bb4, the pin of its base: fmt, Clippy with
-D warningsin every configuration of the library and of the test adapter, and the check thatnvx-rs/README.mdgives each module a short row and a section. At this PR's head:tests/host_modules.rs; the test adapter's protocol-only tests, and its host tests of the library, the binaries,host_sessionandclone_start.guest::cloneandguest::roottests; the test adapter's musl guest library tests andguest_session; and, as root, the ignored test that checks the partition-table reread against the kernel with a loop device, and, in a process of their own, the privileged tests ofguest::mappings.-D warnings, the MSRV checks of the library and of the test adapter's three configurations, and the Markdown links.At #960's head, the top of the stack, with OpenVMM at 832d5bb4:
-D warningswith all features and on the library with and without default features; the tests with all features and with the default ones; the library without default features; the documentation with-D warnings; and the MSRV check, with the aarch64-apple-darwin check on Linux.guest::mappings, ofguest::clone::activation::apply, and of the partition-table reread with a loop device, each in a process of its own:guest::mappings' privileged tests can find a mount busy at their teardown when another test of the same process starts a child, as they do at runtime-adapter: start clones of templates #954's head.devwere skipped.vm_clone, 16 scenarios, with this PR's two: the generated machine IDs, and the view that cannot be set;vm_layered, 43;vm_restart, 3;vm_steps, 3; andvm_template, 9.