Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/acceptance-case-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,16 +478,16 @@ this matrix. The README's own code is no doctest.
| `nvx-rs/src/activation/mod.rs - activation (line 130)` | kept | |
| `nvx-rs/src/exec/mod.rs - exec (line 113)` | retired | The exec engine leaves (SL6.11a) |
| `nvx-rs/src/gpt/mod.rs - gpt (line 43)` | kept | |
| `nvx-rs/src/guest/activation.rs - guest::activation (line 157)` | kept | SL6.11c rewrote it on `guest::snapshot::capture`, when its capture preparation and admission fence left (#1087) |
| `nvx-rs/src/guest/activation/state.rs - guest::activation::state::Activation (line 56)` | kept | |
| `nvx-rs/src/guest/activation.rs - guest::activation (line 151)` | kept | SL6.11c rewrote it on `guest::snapshot::capture`, when its capture preparation and admission fence left (#1087) |
| `nvx-rs/src/guest/activation/state.rs - guest::activation::state::Activation (line 54)` | kept | SL6.13b (#1127) rewrote it without the guest's space, which became private: its agent applies the record in its own space |
| `nvx-rs/src/guest/cgroup.rs - guest::cgroup (line 36)` | retired | `guest::cgroup` leaves (SL6.11b) |
| `nvx-rs/src/guest/entry.rs - guest::entry (line 90)` | retired | `guest::entry` leaves (SL6.11b) |
| `nvx-rs/src/guest/exec.rs - guest::exec (line 79)` | retired | `guest::exec` leaves (SL6.11b) |
| `nvx-rs/src/guest/exec.rs - guest::exec::Execution::adopt (line 619)` | retired | `guest::exec` leaves (SL6.11b) |
| `nvx-rs/src/guest/gpt.rs - guest::gpt (line 18)` | kept | The guest's space becomes private (#1127), whose PR moves it to the space's unit tests |
| `nvx-rs/src/guest/gpt.rs - guest::gpt (line 18)` | kept | SL6.13b (#1127) moved it to the module's unit tests, `nvx-rs/src/guest/gpt/tests.rs::a_disk_s_steps_compile`, since the guest's space became private |
| `nvx-rs/src/guest/mappings/mapper.rs - guest::mappings::mapper (line 107)` | kept | SL6.11d (#1123) moved it to the mapper's unit tests, `nvx-rs/src/guest/mappings/mapper/tests.rs::a_mapper_s_steps_compile`, which map and unmount through the seams that NVX's programs use, since the mapper's `apply` and `teardown`, which only tests used, left |
| `nvx-rs/src/guest/mappings/table.rs - guest::mappings::table::MappingTable (line 47)` | kept | |
| `nvx-rs/src/guest/mod.rs - guest (line 34)` | kept | The guest-agent guidelines quote it |
| `nvx-rs/src/guest/mod.rs - guest (line 33)` | kept | The guest-agent guidelines quote it |
| `nvx-rs/src/guest/nvxs.rs - guest::nvxs (line 22)` | kept | |
| `nvx-rs/src/guest/process.rs - guest::process (line 12)` | retired | `guest::process` leaves (SL6.11b) |
| `nvx-rs/src/guest/process.rs - guest::process (line 39)` | retired | `guest::process` leaves (SL6.11b) |
Expand Down
5 changes: 2 additions & 3 deletions doc/guest-agent-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ nvx-init's time daemon runs for the VM's life.
NVX's space is internal reference code that nvx-init implements, not public
API: the modules that hold it, `guest::root`, `guest::view`, `guest::gpt`, the
mapper of `guest::mappings`, and `guest::activation::apply` with its types,
are hidden from the documentation, unstable, and become private to the crate
once nothing outside NVX uses them. Another init builds its own space, or
adapts this code; it builds on no space module of `nvx`.
are private to the crate. Another init builds its own space, or adapts this
code; it builds on no space module of `nvx`.

The space's inputs are public contracts:

Expand Down
352 changes: 175 additions & 177 deletions nvx-rs/README.md

Large diffs are not rendered by default.

384 changes: 0 additions & 384 deletions nvx-rs/public-api/linux.txt

Large diffs are not rendered by default.

67 changes: 29 additions & 38 deletions nvx-rs/src/guest/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
//! memory size that the repair reports, before anything has a side effect. An Activate whose
//! record the adapter's framing refused unread, as longer than a record can be, fails as a
//! record that long does;
//! 3. activating: the adapter applies the accepted record with `apply`, which
//! 3. activating: the adapter applies the accepted record in its own space, which
//! activates the root and the workload with its settings, as below, passes the activation
//! what the session received meanwhile, which came before Ready, then reports the result to
//! [`Activation::complete`];
Expand Down Expand Up @@ -114,18 +114,15 @@
//!
//! [guest-agent guidelines]: https://github.com/microsoft/nvx/blob/dev/doc/guest-agent-guidelines.md
//!
//! The activation's application, `apply` and its types, is NVX's internal reference code of the
//! guest's space, hidden from this documentation and unstable: it becomes crate-private once
//! nothing outside NVX uses it.
//!
//! `apply` activates the clone as at a cold boot. It assembles the root as
//! `root::assemble` does, with the record's image, an EROFS image whose UUID it checks if the
//! record expects one, or a GPT disk whose GUID 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 the
//! inputs that the guest takes from its own configuration, which its template captured, a
//! `RootTemplate`: the runtime directory, the block devices, the host filesystem device's
//! Each adapter applies the record in its own space. nvx-init applies it with its own, NVX's
//! internal reference code, private to the crate, which activates the clone as at a cold boot. It
//! assembles the root as a cold boot does, with the record's image, an EROFS image
//! whose UUID it checks if the record expects one, or a GPT disk whose GUID 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 the inputs that the guest takes from its own configuration, which
//! its template captured: the runtime directory, the block devices, the host filesystem device's
//! virtio-fs tag, the gateway of the template's NIC, if it has one, and how long the assembly waits
//! for each block device, but never past the activation's deadline, which the adapter passes, so
//! for each block device, but never past the activation's deadline, so
//! that the application ends by then. For a GPT disk, it first asks the kernel to reread the disk's
//! partition table, once the distro device appeared, by the same deadline: the kernel created the
//! block devices of partitions from the template's placeholder, when it found it, and creates those
Expand All @@ -135,22 +132,19 @@
//! sandbox's scratch keeps its upper across boots; the check reads at most one entry of each, and
//! no file. The record holds no execution identity: the adapter gives each execution the identity
//! that the execution names, as for a sandbox without a workload identity at a cold boot. If the
//! record has an export, it mounts the host
//! export through the template's
//! filesystem device, with `Root::mount_export`, read-write, at
//! `hostfs/root` in the runtime directory, outside the root, where only the guest's root user
//! reaches it: the record names no mode, and OpenVMM limits each of the export's children to its
//! own access. It applies the record's VM-wide view through `view::apply`, with the record's
//! DNS server, if any. The root owns the machine-ID and resolver binds, including any made
//! before a later failure. The view sets the VM's hostname and generates an ID only when the
//! record requests one, after the repair's reseed. Application starts no process. It returns
//! the `Applied` root and workload, which the adapter keeps, with the export's root, from which
//! the adapter maps the host's mapping table once the clone is ready, the network's proxy
//! endpoint, which the adapter tells its workloads as at a cold boot, and the publication and
//! gateway with which the caller starts and supervises ingress. The `teardown` of the `Applied`
//! closes the export's root, then tears the root down with its view binds. An application that
//! fails returns an `ApplyError` instead, after which nothing that it mounted or bound stays
//! mounted, unless the error holds the unmounts that failed.
//! record has an export, it mounts the host export through the template's filesystem device,
//! read-write, at `hostfs/root` in the runtime directory, outside the root, where only the guest's
//! root user reaches it: the record names no mode, and OpenVMM limits each of the export's
//! children to its own access. It applies the record's VM-wide view, with the record's DNS server,
//! if any. The root owns the machine-ID and resolver binds, including any made before a later
//! failure. The view sets the VM's hostname and generates an ID only when the record requests
//! one, after the repair's reseed. The application starts no process. nvx-init keeps the root and
//! the workload that it activated, with the export's root, from which it maps the host's mapping
//! table once the clone is ready, the network's proxy endpoint, which it tells its workloads as at
//! a cold boot, and the publication and gateway with which it starts and supervises ingress. Their
//! teardown closes the export's root, then tears the root down with its view binds. An
//! application that fails leaves nothing that it mounted or bound mounted, unless its error holds
//! the unmounts that failed.
//!
//! # Example
//!
Expand Down Expand Up @@ -206,16 +200,16 @@
//! report-only finish without a running time daemon, which runs the deferred time work itself,
//! in a child that it waits for. They never power the VM off, so a caller that is not PID 1 keeps
//! its own reaping, transport, and shutdown. The activation needs no privilege, and
//! makes no system call and changes no admission fence. Its
//! application, `apply`, needs the guest's root user, in the guest's initial mount
//! namespace, as `root::assemble` does: for a GPT disk, it asks the kernel to reread the disk's
//! partition table, which needs `CAP_SYS_ADMIN`; it mounts the root, applies its VM-wide view,
//! and, if it fails, tears it down. It starts no process and generates at most one ID, a
//! makes no system call and changes no admission fence. nvx-init's
//! application of the record needs the guest's root user, in the guest's initial mount
//! namespace, as its assembly of a root does: for a GPT disk, it asks the kernel to reread the
//! disk's partition table, which needs `CAP_SYS_ADMIN`; it mounts the root, applies its VM-wide
//! view, and, if it fails, tears it down. It starts no process and generates at most one ID, a
//! machine ID that the record asks for. It mounts the host export of a record that has one, and
//! writes the machine ID's and the resolver's files in the runtime directory, whose binds over
//! the root's `/etc/machine-id` and `/etc/resolv.conf` need Linux 6.8 or later.

mod state;
pub(crate) mod state;
#[cfg(test)]
mod tests;

Expand All @@ -235,10 +229,7 @@ pub use self::state::{
Activation, ActivationAction, ActivationCause, ActivationFailure, ActivationInput,
ActivationState, MACHINE_ID_FILE, RESOLVER_FILE,
};
#[doc(hidden)]
pub use self::state::{
Applied, AppliedTeardownError, ApplyError, NetworkError, RootTemplate, apply,
};
pub(crate) use self::state::{Applied, ApplyError, RootTemplate, apply};
pub use super::snapshot::{MOUNT_TABLE_LIMIT, Mounted, Origin};
pub use super::snapshot::{
RangeFault, RepairCause, RepairError, RepairProgress, RepairStage, Repaired,
Expand Down
63 changes: 26 additions & 37 deletions nvx-rs/src/guest/activation/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! the host in one Activate, and [`apply`](fn@apply), which activates the clone's root and workload
//! with that configuration, as the [module](super#activation) describes.

mod apply;
pub(crate) mod apply;
#[cfg(test)]
mod tests;

Expand All @@ -17,10 +17,8 @@ use crate::activation::{
ActivationError, ActivationRecord, ExpectedResources, ReadyActivation, TransactionId,
};

pub use self::apply::{
Applied, AppliedTeardownError, ApplyError, MACHINE_ID_FILE, NetworkError, RESOLVER_FILE,
RootTemplate, apply,
};
pub(crate) use self::apply::{Applied, ApplyError, RootTemplate, apply};
pub use self::apply::{MACHINE_ID_FILE, RESOLVER_FILE};

/// The activation of a repaired clone: the state machine, without I/O and independent of any
/// framing, of the clone's control session, which takes the clone's configuration from the host
Expand All @@ -29,8 +27,8 @@ pub use self::apply::{
///
/// The adapter passes it what it receives on the session, each an [`ActivationInput`], through
/// [`input`](Self::input), and does what the [`ActivationAction`] that it returns says. The
/// adapter applies the record of the Activate that the activation accepted, with
/// `apply`, whose waits end by the activation's [`deadline`](Self::deadline), passes
/// adapter applies the record of the Activate that the activation accepted, in its own space,
/// by the activation's [`deadline`](Self::deadline), passes
/// the activation what the session received while the application ran, which came before Ready,
/// then reports the result through [`complete`](Self::complete), whose Ready answers the Activate
/// and each retry that [joined](ActivationAction::Join) it, and it reports the start of the
Expand All @@ -54,13 +52,11 @@ pub use self::apply::{
/// # Example
///
/// ```no_run
/// use std::error::Error;
/// use std::time::Instant;
///
/// use nvx::activation::ReadyActivation;
/// use nvx::guest::activation::{
/// self, Activation, ActivationAction, ActivationFailure, ActivationInput,
/// Applied, RootTemplate,
/// };
/// use nvx::activation::{ActivationRecord, ReadyActivation};
/// use nvx::guest::activation::{Activation, ActivationAction, ActivationFailure, ActivationInput};
///
/// /// What the agent's control session received from the host, in the agent's own framing.
/// enum Received {
Expand All @@ -87,12 +83,16 @@ pub use self::apply::{
/// /// Queues the acknowledgement of the session's reset, and abandons the earlier epoch's
/// /// work.
/// fn acknowledge(&mut self);
/// /// Keeps the clone's activated root and workload: the identity, if any, in which it runs
/// /// every execution, with the hostname of every execution, the root, with the runtime's
/// /// binds on it, which `Applied::teardown` removes before it tears the root down, and the
/// /// export's root, which `Applied::take_export` hands over, from which it maps the host's
/// /// mapping table.
/// fn keep(&mut self, applied: Applied);
/// /// Activates the clone's root and workload with the settings of `record` in the agent's
/// /// own space, whose waits for the devices end by `deadline`, and keeps them: the identity,
/// /// if any, in which it runs every execution, with the hostname of every execution, the
/// /// root, and the export's root, from which it maps the host's mapping table. nvx-init's
/// /// space, NVX's internal reference code, is private to `nvx`.
/// fn activate(
/// &mut self,
/// record: &ActivationRecord,
/// deadline: Option<Instant>,
/// ) -> Result<(), Box<dyn Error + Send + Sync>>;
/// /// Queues Ready, which reports `activation` beside the agent's own identities, such as its
/// /// build.
/// fn ready(&mut self, activation: ReadyActivation);
Expand All @@ -101,13 +101,8 @@ pub use self::apply::{
/// fn serve(&mut self, message: Vec<u8>) -> bool;
/// }
///
/// /// Serves the control session of the clone whose activation is `activation`, and whose
/// /// template captured `template`, the inputs of its root's assembly, until it fails.
/// fn serve(
/// agent: &mut impl Agent,
/// template: &RootTemplate,
/// mut activation: Activation,
/// ) -> Result<(), ActivationFailure> {
/// /// Serves the control session of the clone whose activation is `activation`, until it fails.
/// fn serve(agent: &mut impl Agent, mut activation: Activation) -> Result<(), ActivationFailure> {
/// loop {
/// let Some(received) = agent.receive(activation.deadline()) else {
/// // The deadline passed, unless the clone is ready, which ends the activation.
Expand All @@ -122,25 +117,19 @@ pub use self::apply::{
/// // effects start here. The
/// // application waits for the devices until the activation's deadline at the
/// // latest, so that it ends by then.
/// let applied = activation::apply(&record, template, activation.deadline());
/// let applied = agent.activate(&record, activation.deadline());
/// // What the host sent meanwhile came before Ready: a retry of the Activate,
/// // byte for byte, joins the application, and anything else, a reset included,
/// // fails the activation, which drops what the application assembled.
/// // fails the activation.
/// let mut joined = 0;
/// while let Some(received) = agent.receive(Some(Instant::now())) {
/// let action = activation.input(Instant::now(), input(&received))?;
/// if action == ActivationAction::Join {
/// joined += 1;
/// }
/// }
/// let ready = match applied {
/// Ok(applied) => {
/// agent.keep(applied);
/// activation.complete(Instant::now(), Ok(()))?
/// }
/// // A failed application fails the activation.
/// Err(error) => activation.complete(Instant::now(), Err(error.into()))?,
/// };
/// // A failed application fails the activation.
/// let ready = activation.complete(Instant::now(), applied)?;
/// // Ready answers the Activate, and each retry that joined its application.
/// for _ in 0..=joined {
/// agent.ready(ready);
Expand Down Expand Up @@ -484,8 +473,8 @@ pub enum ActivationAction {
/// The activation took the input, which needs nothing more of the adapter.
Continue,
/// The activation accepted the host's Activate, whose record decoded and validated, and
/// which nothing before had any side effect of: the adapter applies the record with
/// `apply`, which activates the root and the workload with its settings, passes
/// which nothing before had any side effect of: the adapter applies the record in its own
/// space, which activates the root and the workload with its settings, passes
/// the activation what the session received meanwhile, which came before Ready, then reports
/// the result to [`Activation::complete`].
Apply(Box<ActivationRecord>),
Expand Down
Loading
Loading