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.

28 changes: 16 additions & 12 deletions nvx-rs/src/guest/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,13 @@
//! request or another reset, fails the activation. The activation decodes the Activate's
//! 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;
//! 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`](fn@apply), which
//! activates the root and the workload with its settings, as below, then reports the result
//! to [`Activation::complete`];
//! 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`];
//! 4. ready, with the mappings pending: the activation completed, opened the fence, and returned
//! what Ready reports beside the adapter's own identities, such as its build, a
//! [`ReadyActivation`](crate::activation::ReadyActivation): that the activation applied, with
Expand All @@ -139,15 +142,16 @@
//! 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
//! [`apply`](fn@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, 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 each, but never past the activation's deadline, which the adapter
//! passes, so that the application ends by then. 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
Expand Down
110 changes: 83 additions & 27 deletions nvx-rs/src/guest/clone/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ pub use self::apply::{Applied, ApplyError, RootTemplate, 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`](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.
/// [`apply`](fn@apply), whose waits end 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
/// sandbox, which its own order takes once the activation is ready, through
/// [`start`](Self::start). The adapter's loop wakes by the 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 Down Expand Up @@ -66,20 +68,27 @@ pub use self::apply::{Applied, ApplyError, RootTemplate, apply};
/// Partial,
/// /// The host's Activate, with its record's bytes.
/// Activate(Vec<u8>),
/// /// The host's Activate, whose header announced this many bytes, more than a record has,
/// /// which the framing refused before it read any of them.
/// TooLong(usize),
/// /// Any other message of the host, such as a part of the mapping table, or the start.
/// Other(Vec<u8>),
/// }
///
/// /// The agent of a clone, which serves its control session in its own framing.
/// /// The agent of a clone, which serves its control session in its own framing, and queues what
/// /// it sends, so that a host that reads nothing holds up neither the agent nor the deadline.
/// trait Agent {
/// /// Waits until the host sends something, or until `deadline`, and returns what it sent.
/// /// Waits until the host sends something, or until `deadline`, while it writes what the
/// /// agent queued, and returns what the host sent, or `None` once the deadline passed. A
/// /// deadline that passed already returns only what the host sent already.
/// fn receive(&mut self, deadline: Option<Instant>) -> Option<Received>;
/// /// Acknowledges the session's reset, and abandons the earlier epoch's work.
/// /// 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 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
/// /// Queues 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
Expand All @@ -100,27 +109,37 @@ pub use self::apply::{Applied, ApplyError, RootTemplate, apply};
/// activation.tick(Instant::now())?;
/// continue;
/// };
/// let input = match &received {
/// Received::Reset => ActivationInput::Reset,
/// Received::Partial => ActivationInput::Flow,
/// Received::Activate(record) => ActivationInput::Activate(record),
/// Received::Other(_) => ActivationInput::Request,
/// };
/// match activation.input(Instant::now(), input)? {
/// match activation.input(Instant::now(), input(&received))? {
/// ActivationAction::Acknowledge => agent.acknowledge(),
/// ActivationAction::Continue | ActivationAction::Join => {}
/// 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 ready = match clone::apply(&record, template) {
/// // effects start here, and the fence opens once they all succeeded. The
/// // application waits for the devices until the activation's deadline at the
/// // latest, so that it ends by then.
/// let applied = clone::apply(&record, template, 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.
/// 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: the fence stays closed.
/// Err(error) => activation.complete(Instant::now(), Err(error.into()))?,
/// };
/// agent.ready(ready);
/// // Ready answers the Activate, and each retry that joined its application.
/// for _ in 0..=joined {
/// agent.ready(ready);
/// }
/// }
/// ActivationAction::Ready(ready) => agent.ready(ready),
/// // The activation serves only another message, once it is ready.
Expand All @@ -134,6 +153,17 @@ pub use self::apply::{Applied, ApplyError, RootTemplate, apply};
/// }
/// }
/// }
///
/// /// Returns the activation's input of `received`.
/// fn input(received: &Received) -> ActivationInput<'_> {
/// match received {
/// Received::Reset => ActivationInput::Reset,
/// Received::Partial => ActivationInput::Flow,
/// Received::Activate(record) => ActivationInput::Activate(record),
/// Received::TooLong(length) => ActivationInput::TooLong(*length),
/// Received::Other(_) => ActivationInput::Request,
/// }
/// }
/// ```
pub struct Activation<F> {
/// The caller's fence of workload admission, closed until the activation is ready.
Expand Down Expand Up @@ -163,12 +193,14 @@ impl<F: AdmissionFence> Activation<F> {
cpus: repaired.cpus(),
memory: repaired.memory(),
};
Self::of(resources, repaired.acknowledged_at(), fence, timeout, now)
Self::with_resources(resources, repaired.acknowledged_at(), fence, timeout, now)
}

/// Returns the activation of a clone that has `resources`, and whose repair acknowledged its
/// restore at `acknowledged_at`, if it did, as [`new`](Self::new) does.
fn of(
/// restore at `acknowledged_at`, if it did, as [`new`](Self::new) does with the resources and
/// the acknowledgement of a [`Repaired`] clone: for a caller that has them otherwise, such as
/// the tests of an adapter's framing, which run its session without a restore.
pub fn with_resources(
resources: ExpectedResources,
acknowledged_at: Option<Instant>,
fence: F,
Expand Down Expand Up @@ -213,6 +245,11 @@ impl<F: AdmissionFence> Activation<F> {
/// acknowledges, a byte-equal retry of the Activate gets the cached result, what Ready
/// reports, without activating again, and any other Activate fails as while activating.
/// Every other request goes to the adapter's order, as after a cold boot.
///
/// An Activate whose record the adapter's framing refused unread, as longer than a record can
/// be, [`ActivationInput::TooLong`], fails as one whose record has that many bytes: in the
/// transport bootstrap with [`ActivationCause::Request`], and from then on with
/// [`ActivationCause::Record`].
pub fn input(
&mut self,
now: Instant,
Expand All @@ -234,12 +271,24 @@ impl<F: AdmissionFence> Activation<F> {
ActivationState::Activating | ActivationState::Ready | ActivationState::Started,
ActivationInput::Activate(record),
) => self.retry(record),
// A record that long never decodes, so its Activate fails as one whose bytes the
// adapter read would, whether the activation accepted another or not.
(
ActivationState::Restricted
| ActivationState::Activating
| ActivationState::Ready
| ActivationState::Started,
ActivationInput::TooLong(length),
) => Err(self.fail(ActivationCause::Record(ActivationError::TooLong(length)))),
(ActivationState::Ready | ActivationState::Started, ActivationInput::Request) => {
Ok(ActivationAction::Serve)
}
(_, ActivationInput::Activate(_) | ActivationInput::Request) => {
Err(self.fail(ActivationCause::Request))
}
(
_,
ActivationInput::Activate(_)
| ActivationInput::TooLong(_)
| ActivationInput::Request,
) => Err(self.fail(ActivationCause::Request)),
}
}

Expand Down Expand Up @@ -415,6 +464,12 @@ pub enum ActivationInput<'a> {
/// The host's Activate, with the bytes of its activation record, the canonical encoding of an
/// [`ActivationRecord`].
Activate(&'a [u8]),
/// The host's Activate, whose record the adapter's framing refused before it took any byte of
/// it, since its header announced this many bytes, more than
/// [`MAX_RECORD_BYTES`](crate::activation::MAX_RECORD_BYTES): the activation takes it as an
/// Activate whose record's bytes are that long, which [`ActivationRecord::decode`] refuses
/// with [`ActivationError::TooLong`].
TooLong(usize),
/// Any other request of the host, such as a part of the mapping table, the start of the
/// sandbox, an execution, or a probe of the adapter's framing.
Request,
Expand All @@ -431,8 +486,9 @@ pub enum ActivationAction {
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`](fn@apply), which activates the root and the workload with its settings, then
/// reports the result to [`Activation::complete`].
/// [`apply`](fn@apply), 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>),
/// 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