Skip to content
Open
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
129 changes: 88 additions & 41 deletions tests/runtime-adapter/README.md

Large diffs are not rendered by default.

81 changes: 57 additions & 24 deletions tests/runtime-adapter/src/bin/runtime-adapter-host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@
//! address and the policy that OpenVMM enforces there, in the serde form of a plan's network,
//! which the launch reads before anything else, so that it refuses a network that the plan
//! refuses before it needs NVX's artifacts or the store, and puts on the adapter's plan, which
//! renders it; see `runtime_adapter::layered::read_network`. Before it spawns the VM, the launch
//! checks the bindings that STORE stages for the image and the scratch image, from their metadata
//! alone, and fails if STORE stages none for either, or one no longer holds, as for an image that
//! changed after it was staged. It hands OpenVMM the files that the checks opened, which it holds
//! until the guest has assembled the root, or until it has torn down a launch that failed, and
//! records the plan, with its network, and the bindings.
//! renders it; see `runtime_adapter::layered::read_network`. The root configuration says whether
//! the guest's listeners are published through the network, as OpenVMM publishes them, and
//! unpublished without one; see `runtime_adapter::layered::LayeredLaunch::with_network`. Before
//! it spawns the VM, the launch checks the bindings that STORE stages for the image and the
//! scratch image, from their metadata alone, and fails if STORE stages none for either, or one no
//! longer holds, as for an image that changed after it was staged. It hands OpenVMM the files
//! that the checks opened, which it holds until the guest has assembled the root, or until it has
//! torn down a launch that failed, and records the plan, with its network, and the bindings.
//!
//! SHARES, `--share HOST:TARGET[:ro]... [--deny PATH]... [--reserved TARGET]...
//! [--share-root DIR]...`, shares each host directory or regular file HOST with the root, at the
Expand Down Expand Up @@ -89,14 +91,18 @@
//! ID GID, with the supplementary groups GIDs and the capabilities numbered N, such as 0 for
//! `CAP_CHOWN`, if given, and the guest checks its account, unless `--numeric` says that the IDs
//! have none. It starts in the root's directory DIR, or `/`, with exactly the environment of its
//! `--env` entries. It runs for at most a minute, with 16 KiB chunks and the credit of 4 of them
//! for its input and 8 for its output, which its standard output and error share. The command
//! keeps at most 16 MiB of each stream, and cancels an execution whose stream would exceed them;
//! see `runtime_adapter::exec::MAX_CAPTURE`. Execution N, from 1, has the key `(B + N, E)`: E is
//! the epoch of the command's attachment, and B the first 8 bytes of the broker instance's ID, as
//! a little-endian number. The guest refuses a key that it heard of before, and the epochs of a
//! broker instance only advance, while a broker restored from a snapshot has a new instance ID,
//! so no command repeats the key of another's execution.
//! `--env` entries, then, if the VM's network has a proxy, the proxy's URL as the guest sees it
//! in `http_proxy`, `https_proxy`, `HTTP_PROXY`, and `HTTPS_PROXY`, unless the entries name any
//! of them; see `runtime_adapter::layered::with_proxy`. Before it attaches, the command checks
//! that the framing still carries each start with the proxy's URL, and runs none of the
//! executions if one no longer fits. It runs for at most a minute, with 16 KiB chunks and the
//! credit of 4 of them for its input and 8 for its output, which its standard output and error
//! share. The command keeps at most 16 MiB of each stream, and cancels an execution whose stream
//! would exceed them; see `runtime_adapter::exec::MAX_CAPTURE`. Execution N, from 1, has the key
//! `(B + N, E)`: E is the epoch of the command's attachment, and B the first 8 bytes of the
//! broker instance's ID, as a little-endian number. The guest refuses a key that it heard of
//! before, and the epochs of a broker instance only advance, while a broker restored from a
//! snapshot has a new instance ID, so no command repeats the key of another's execution.
//!
//! Each command but `stage` takes NVX's artifacts from the directory that `NVX_ARTIFACTS_DIR`
//! names, a repository checkout or an extracted release, and the VM's runtime identity from NVX's
Expand Down Expand Up @@ -176,7 +182,9 @@ use runtime_adapter::frame::{MAX_BODY, Message};
use runtime_adapter::hook::{Step, StepHook};
use runtime_adapter::host::{self, Client, ClientError, Executions, OrderError};
use runtime_adapter::initramfs;
use runtime_adapter::layered::{LaunchRecord, LayeredLaunch, Share, Shares, Staged, read_network};
use runtime_adapter::layered::{
LaunchRecord, LayeredLaunch, Share, Shares, Staged, read_network, with_proxy,
};
use runtime_adapter::probe::{Probe, ProbeReply, escape};
use runtime_adapter::root::{JournalPolicy, OverlayMode, RootFailure};
use runtime_adapter::runtime::Runtime;
Expand Down Expand Up @@ -341,6 +349,17 @@ impl Execution {
}
}

/// Checks that the guest takes `start`: that it decodes it, as it decodes an execution's start,
/// and that the framing carries it. The guest ends its session on a start that it cannot decode.
fn check_start(start: &Start) -> Result<(), String> {
let encoded = start.encode();
Start::decode(&encoded).map_err(|error| error.problem().to_owned())?;
Message::ExecStart(encoded)
.encode()
.map_err(|error| format!("the start of an execution is too long: {error}"))?;
Ok(())
}

/// Returns the limits of an execution of `exec`: chunks of [`EXEC_CHUNK`] bytes, and frame-counted
/// credit, of [`EXEC_INPUT_FRAMES`] for the input, and of [`EXEC_OUTPUT_FRAMES`] in one pool that
/// standard output and error share.
Expand Down Expand Up @@ -816,11 +835,7 @@ fn parse_exec(
// one that breaks the rules of the encoding, as a relative working directory or an
// environment entry without a name does, or that the framing does not carry, before it
// attaches.
let encoded = execution.start(Key::new(0, 0)).encode();
Start::decode(&encoded).map_err(|error| error.problem().to_owned())?;
Message::ExecStart(encoded)
.encode()
.map_err(|error| format!("the start of an execution is too long: {error}"))?;
check_start(&execution.start(Key::new(0, 0)))?;
executions.push(execution);
if !more {
break;
Expand Down Expand Up @@ -920,8 +935,7 @@ fn launch(
if let Some(export) = layered.shares.plan()? {
launch.share(export)?;
}
launch.plan = launch.plan.with_network(network);
Ok(launch)
Ok(launch.with_network(network))
})
.transpose()
.map_err(|error| describe(&error))?;
Expand Down Expand Up @@ -1284,6 +1298,24 @@ fn exec(store: &Path, guest_build: &str, executions: &[Execution]) -> Result<Exi
Ok(vmm) => vmm,
Err(status) => return Ok(status),
};
// Each execution finds the URL of the proxy of the VM's network, if any, in its environment,
// from the plan that the launch recorded. The proxy's variables lengthen a start, so the
// command checks every start again before it attaches, rather than fail once it sent the
// starts before it. A start takes its key once the command attached, and a key's encoding has
// the same size whatever its value.
let plan = LaunchRecord::read_plan(store.dir()).map_err(|error| describe(&error))?;
let mut starts = Vec::with_capacity(executions.len());
for (number, execution) in (1..).zip(executions) {
let start = execution.start(Key::new(0, 0));
let start = match &plan {
Some(plan) => with_proxy(start, plan),
None => start,
};
check_start(&start).map_err(|problem| {
format!("execution {number}, with the proxy of the VM's network: {problem}")
})?;
starts.push(start);
}
let deadline = Instant::now() + SESSION_TIMEOUT;
let capability = store.read_capability().map_err(|error| describe(&error))?;
let mut client = connect(&vmm, &capability, deadline)?;
Expand All @@ -1293,10 +1325,11 @@ fn exec(store: &Path, guest_build: &str, executions: &[Execution]) -> Result<Exi
let base = u64::from_le_bytes(base.try_into().expect("an instance ID has 16 bytes"));
let mut running = Executions::new();
let mut ids = Vec::new();
for (number, execution) in (1..).zip(executions) {
for (number, mut start) in (1..).zip(starts) {
let key = Key::new(base.wrapping_add(number), identity.epoch);
start.key = key;
running
.start(&mut client, execution.start(key), exec_timers(), deadline)
.start(&mut client, start, exec_timers(), deadline)
.map_err(|error| format!("cannot start execution {number}: {}", describe(&error)))?;
ids.push(key.id());
}
Expand Down
Loading
Loading