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
2 changes: 1 addition & 1 deletion nvx-rs/README.md

Large diffs are not rendered by default.

37 changes: 29 additions & 8 deletions nvx-rs/src/guest/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,14 @@
//! record with [`ActivationRecord::decode`](crate::activation::ActivationRecord::decode), and
//! accepts it only as a clone's, with a restore section, that expects the CPU count and the
//! memory size that the repair reports, before anything has a side effect;
//! 3. activating: the adapter applies the accepted record, activating the root and the workload
//! with its settings, then reports the result to [`Activation::complete`];
//! 3. activating: the adapter applies the accepted record with [`apply`](fn@apply), which
//! activates the root and the workload with its settings, as below, then reports the result
//! to [`Activation::complete`];
//! 4. ready, with the mappings pending: the activation completed, opened the fence, and returned
//! what Ready reports, a [`ReadyActivation`](crate::activation::ReadyActivation): that the
//! activation applied, with the record's transaction ID. The host's mapping table, which the
//! record announced, and the start of the sandbox follow in the adapter's order, as after a
//! cold boot;
//! what Ready reports beside the adapter's own identities, such as its build, a
//! [`ReadyActivation`](crate::activation::ReadyActivation): that the activation applied, with
//! the record's transaction ID. The host's mapping table, which the record announced, and the
//! start of the sandbox follow in the adapter's order, as after a cold boot;
//! 5. started: the adapter's order started the sandbox, which [`Activation::start`] records.
//!
//! The host retries an Activate with the same transaction ID and a byte-equal record, and such a
Expand All @@ -138,6 +139,23 @@
//! and its [`ActivationCause`], and which every later call returns again. The activation never
//! powers the VM off: the adapter reports the failure, and ends the VM.
//!
//! [`apply`](fn@apply) activates the clone as at a cold boot. It assembles the root with
//! [`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 the inputs that the guest takes from its own configuration, which its
//! template captured, a [`RootTemplate`]: the runtime directory, the block devices, and how long
//! the assembly waits for them. It refuses a GPT disk before anything has a side effect: the
//! kernel created the block devices of a disk's partitions when it found the template's
//! placeholder. If the record has a workload identity, it then checks its account in the
//! assembled root, before Ready, as NVX's init checks it at boot: the identity is
//! [`Identity::new`](super::entry::Identity::new) of the record's user and group IDs, without
//! supplementary groups or capabilities, in existing-account mode, and every execution runs as
//! it. Without one, the adapter gives each execution its own identity, which
//! [`Entry::check`](super::entry::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, its network, its hostname, and its machine ID.
//!
//! # Example
//!
//! ```no_run
Expand Down Expand Up @@ -218,7 +236,10 @@
//! preparation, which stops the VM unless the ABI is report-only, as [strict
//! mode](#strict-mode) tells, 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: of the clone, it changes only the caller's fence, which it opens.
//! system call: of the clone, it changes only the caller's fence, which it opens. Its
//! application, [`apply`](fn@apply), needs the guest's root user, in the guest's initial mount
//! namespace, as [`root::assemble`] does: it mounts the root, reads the account databases below
//! it, and, if it fails, tears it down. It starts no process and generates no ID.

mod activation;
mod mounts;
Expand All @@ -242,7 +263,7 @@ use super::time::{

pub use self::activation::{
Activation, ActivationAction, ActivationCause, ActivationFailure, ActivationInput,
ActivationState,
ActivationState, Applied, ApplyError, RootTemplate, apply,
};
pub use self::mounts::{Mounted, Origin};
pub use self::repair::{
Expand Down
51 changes: 33 additions & 18 deletions nvx-rs/src/guest/clone/activation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! The activation of a repaired clone, in the caller's process: [`Activation`], the state machine
//! of the clone's restricted control session, through which the clone takes its configuration from
//! the host in one Activate, as the [module](super#activation) describes.
//! 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;
#[cfg(test)]
mod tests;

Expand All @@ -15,18 +17,21 @@ use crate::activation::{
ActivationError, ActivationRecord, ExpectedResources, ReadyActivation, TransactionId,
};

pub use self::apply::{Applied, ApplyError, RootTemplate, apply};

/// 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
/// in one Activate, behind the caller's fence of workload admission, which it opens once the
/// activation is ready; see the [module](super#activation).
///
/// 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, and reports the
/// result through [`complete`](Self::complete), and it reports the start of the sandbox, which
/// its own order takes once the activation is ready, through [`start`](Self::start). The
/// adapter's loop wakes by the [`deadline`](Self::deadline), and [ticks](Self::tick) the
/// activation then, whether input arrives or not, and whether its output drains or not.
/// adapter applies the record of the Activate that the activation accepted, with
/// [`apply`](fn@apply), and reports the result through [`complete`](Self::complete), and it
/// reports the start of the sandbox, which its own order takes once the activation is ready,
/// through [`start`](Self::start). The adapter's loop wakes by the [`deadline`](Self::deadline),
/// and [ticks](Self::tick) the activation then, whether input arrives or not, and whether its
/// output drains or not.
///
/// It goes through the [`ActivationState`]s in order, and each takes these inputs:
///
Expand All @@ -45,12 +50,12 @@ use crate::activation::{
/// # Example
///
/// ```no_run
/// use std::error::Error;
/// use std::time::Instant;
///
/// use nvx::activation::{ActivationRecord, ReadyActivation};
/// use nvx::activation::ReadyActivation;
/// use nvx::guest::clone::{
/// Activation, ActivationAction, ActivationFailure, ActivationInput, AdmissionFence,
/// self, Activation, ActivationAction, ActivationFailure, ActivationInput, AdmissionFence,
/// Applied, RootTemplate,
/// };
///
/// /// What the agent's control session received from the host, in the agent's own framing.
Expand All @@ -71,18 +76,22 @@ use crate::activation::{
/// fn receive(&mut self, deadline: Option<Instant>) -> Option<Received>;
/// /// Acknowledges the session's reset, and abandons the earlier epoch's work.
/// fn acknowledge(&mut self);
/// /// Activates the root and the workload with the settings of `record`.
/// fn apply(&mut self, record: &ActivationRecord) -> Result<(), Box<dyn Error + Send + Sync>>;
/// /// Sends Ready, which reports `activation`.
/// /// Keeps the clone's activated root, and the identity, if any, in which it runs every
/// /// execution.
/// fn keep(&mut self, applied: Applied);
/// /// Sends Ready, which reports `activation` beside the agent's own identities, such as its
/// /// build.
/// fn ready(&mut self, activation: ReadyActivation);
/// /// Serves `message` in the agent's order, as after a cold boot, and returns whether the
/// /// host started the sandbox with it.
/// fn serve(&mut self, message: Vec<u8>) -> bool;
/// }
///
/// /// Serves the control session of the clone whose activation is `activation`, until it fails.
/// /// 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<F: AdmissionFence>(
/// agent: &mut impl Agent,
/// template: &RootTemplate,
/// mut activation: Activation<F>,
/// ) -> Result<(), ActivationFailure> {
/// loop {
Expand All @@ -103,8 +112,14 @@ use crate::activation::{
/// ActivationAction::Apply(record) => {
/// // The record is valid, and nothing happened before: the activation's side
/// // effects start here, and the fence opens once they all succeeded.
/// let result = agent.apply(&record);
/// let ready = activation.complete(Instant::now(), result)?;
/// let ready = match clone::apply(&record, template) {
/// Ok(applied) => {
/// agent.keep(applied);
/// activation.complete(Instant::now(), Ok(()))?
/// }
/// // A failed application fails the activation: the fence stays closed.
/// Err(error) => activation.complete(Instant::now(), Err(error.into()))?,
/// };
/// agent.ready(ready);
/// }
/// ActivationAction::Ready(ready) => agent.ready(ready),
Expand Down Expand Up @@ -415,9 +430,9 @@ 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, activating
/// the root and the workload with its settings, then reports the result to
/// [`Activation::complete`].
/// which nothing before had any side effect of: the adapter applies the record with
/// [`apply`](fn@apply), which activates the root and the workload with its settings, then
/// reports the result to [`Activation::complete`].
Apply(Box<ActivationRecord>),
/// A retry of the accepted Activate joined the activation that runs: what
/// [`Activation::complete`] returns answers it too, as the adapter's framing answers each
Expand Down
Loading
Loading