Skip to main content

Sandbox

Struct Sandbox 

Source
pub struct Sandbox { /* private fields */ }
Expand description

One MicroVM’s whole life.

See the module docs for the state machine, the window, and why teardown is explicit.

Implementations§

Source§

impl Sandbox

Source

pub fn with_control_plane(control: ControlPlane) -> Sandbox

A sandbox over an already-built ControlPlane.

The seam every test uses, and public for the same reason ControlPlane::from_ports is: a caller who wants a non-default port, or a fake transport, has already built the plane, and a sandbox constructible only from a Region would be a sandbox no test can drive without AWS. Sandbox::new(region), Sandbox::adopt_in and Sandbox::from_name, which resolve credentials for a region, are in microvms_core::prelude.

The plane’s own clock is what times the suspended window, rather than a second one this type would own. Two clocks in one lifecycle is the trap the daemon’s turmoil tests record: a window measured on one clock while the poll loop sleeps on another calls a closed window open.

Source

pub fn with_build_services(self, services: Arc<dyn BuildServices>) -> Sandbox

Routes Sandbox::ensure_image’s STS and S3 calls through services.

The seam beside with_control_plane’s transport: a test that scripts the control plane replaces the two calls outside it here, and production builds [crate::control::SignedBuildServices] on the first ensure_image.

Source

pub fn with_session_backend(self, backend: Arc<dyn HttpBackend>) -> Sandbox

Routes the session Sandbox::run builds through backend instead of real HTTP.

The daemon-side half of the test seam with_control_plane opens: scripting the control plane gets a test through the launch, and this gets it through everything the launched session then does — uploads, execs, downloads — without a daemon.

Source

pub fn lifecycle(&self) -> Lifecycle

The lifecycle state, which is the symspec’s vm_state.

Source

pub fn token_installed(&self) -> bool

Whether the agent token has been installed (STATE-2).

Source

pub fn image_exists(&self) -> bool

Whether an image is recorded as existing (STATE-1).

Source

pub fn was_terminated(&self) -> bool

Whether this VM was ever terminated (STATE-11).

Source

pub fn bootstrap_count(&self) -> u32

How many times the token has been installed. Never above one (STATE-3).

Source

pub fn image(&self) -> Option<&Image>

The image, once built.

Source

pub fn launch_image_arn(&self) -> Option<&str>

The image ARN Sandbox::run sent in RunMicrovm: the caller’s identifier with a bare name resolved, or the built image’s.

Recorded before the call rather than read back from Sandbox::microvm, which holds only what an accepted launch answered: a launch the service refused, or one a caller interrupted, still names the image it asked for here. None until run has resolved an image, and on an adopted sandbox. Not the image Sandbox::terminate deletes, which is only ever one this sandbox built (Sandbox::image).

Source

pub fn microvm(&self) -> Option<&Microvm>

The VM as the service last described it.

Source

pub fn session(&self) -> Option<&Session>

The session, once launched.

Source

pub fn suspended_window(&self) -> Option<Duration>

The suspended window this sandbox asked for at launch, once it has launched.

Source

pub fn watch_lifecycle(&self) -> Receiver<Lifecycle>

A receiver that sees every lifecycle transition without taking this sandbox’s lock.

A binding’s keepalive reads it between polls: a sandbox busy inside a long exec holds the lock the whole time, which is exactly when the keepalive must keep polling, and a suspend or terminate must end the keepalive before its next poll auto-resumes the VM.

Source

pub fn idle_window(&self) -> Option<Duration>

The idle window this sandbox asked for at launch, once it has launched.

Source

pub fn tunnel_identity(&self) -> Option<&TunnelIdentity>

The tunnel identity, when the launch asked for one (RunRequest::identity).

What tunnel --verify-identity verifies with: the host’s secret and the VM’s public pin. None on a launch that did not ask, and the tunnel route then refuses an identity request with close code 4401 rather than downgrading.

Source

pub fn egress_posture(&self) -> EgressPosture

The egress posture of this sandbox’s launch (BIND-12), which its session carries.

The launch’s own RunRequest::egress_posture once Sandbox::run was accepted. Otherwise EgressPosture::default, unsealed: an adopted sandbox never saw the launch options, and the weakest true claim is the only one it can make.

Source

pub fn port(&self) -> u16

The agent port the control plane was built with: the hooks port on every image this sandbox builds, and the port the daemon listens on in every VM it launches. Read-only; agents::AgentVm needs it to derive a Dockerfile whose AGENTD_PORT agrees with the launch.

Source

pub async fn build_image( &mut self, request: CreateImageRequest, ) -> Result<&Image, Error>

Builds an image and waits for it to become usable.

Every local guard runs inside ControlPlane::create_image, before the call — which matters because the create happens after the caller’s artifact upload, so a rejection AWS raises costs the upload first.

Source

pub async fn ensure_image( &mut self, request: EnsureImageRequest, ) -> Result<EnsuredImage, Error>

Builds or reuses the content-addressed image for request (#221): the name, the decision after each describe, the upload and the create race are crate::control::ensure’s.

Source

pub fn build_artifact_for( &self, request: &CreateImageRequest, ) -> Result<Vec<u8>, Error>

The artifact bytes to upload to the request’s code_artifact_uri.

The upload is the caller’s on this path; Sandbox::ensure_image uploads for itself.

Source

pub async fn upload_artifact_for( &mut self, request: &CreateImageRequest, ) -> Result<(), Error>

Builds the artifact for request and puts it at its code_artifact_uri, through the build services ensure_image uploads with (#258): for a build under a name the caller chose, where no ensure derives the key. The URI must be s3://<bucket>/<key>.

Run Self::preflight first: a request this client refuses then costs no upload.

Source

pub fn preflight(&self, request: &CreateImageRequest) -> Result<(), Error>

Every local guard Self::build_image runs, callable before the artifact upload.

A delegation to ControlPlane::preflight, on this type so the CLI’s build path reaches it through the one sandbox it already holds. Local; zero calls. The point is ordering: build_image’s guards run after the caller’s upload, so a request this client itself refuses would still cost the S3 PUT unless the caller checks first.

Source

pub async fn resolve_image_arn(&self, identifier: &str) -> Result<String, Error>

The ARN for identifier: an ARN passes through with zero calls, a bare name is resolved through the image listing by exact match.

A read-only delegation to ControlPlane::resolve_image_arn, the resolution Sandbox::run makes itself, for a caller that wants the ARN without launching. It touches none of the five state-machine variables: resolution is a question about the account, not about this VM’s lifecycle.

Source

pub async fn find_image_by_name( &self, name: &str, ) -> Result<Option<MicrovmImageSummaryWire>, Error>

The image with exactly name, or None. Read-only; see ControlPlane::find_image_by_name for the pagination and exact-match rules.

Source

pub fn artifact_content_hash_for(&self, request: &CreateImageRequest) -> String

The content hash build --reuse keys an image name to. Local; zero calls.

Source

pub fn dockerfile_for(&self, request: &CreateImageRequest) -> String

The Dockerfile request builds with; see ControlPlane::dockerfile_for. Local; zero calls.

Source

pub async fn managed_base_versions( &self, base_image_arn: &str, ) -> Result<Vec<ManagedMicrovmImageVersionWire>, Error>

The versions of a managed base image, for pinning CreateImageRequest::base_image_version.

A read-only delegation to crate::control::ControlPlane::managed_base_versions, on this type for the reason Self::resolve_image_arn gives: it touches none of the five state-machine variables, because a question about AWS’s published bases is not a question about this VM’s lifecycle.

Source

pub async fn image_version( &self, identifier: &str, version: &str, ) -> Result<MicrovmImageVersionSummaryWire, Error>

One version’s configuration, state, and availability status. Read-only.

Source

pub async fn run(&mut self, request: RunRequest) -> Result<&mut Session, Error>

Launches a MicroVM, waits for RUNNING and for its daemon to answer, and returns its session.

§The three state requirements this is

STATE-1: the accepted launch moves the lifecycle to PENDING and records the image as existing. STATE-2: the platform reporting RUNNING is what marks the token installed — not the launch call, because the run hook is what delivers it and a launch that dies during startup delivered nothing. STATE-3: bootstrap_count is incremented exactly here, and a second run on the same sandbox is refused, which is what makes “at most once per VM lifetime” a property of the type rather than of a caller’s discipline.

The agent token rides in runHookPayload, which is what keeps it out of the shared image snapshot. That is safe because the platform forwards no external traffic until the run hook returns 200, so a per-VM secret delivered at launch wins the first-writer race through the endpoint.

§A bare image name

RunRequest::image_identifier may be a name: after the local refusals it is resolved through ControlPlane::resolve_image_arn, and Sandbox::launch_image_arn records the ARN sent. A name that resolves to nothing fails with ERR_PRECONDITION and leaves the sandbox as it was, so a later run on it can still launch.

Source

pub async fn wait_until_running( &mut self, timeout: Duration, ) -> Result<&mut Session, Error>

Waits for a launch accepted by Sandbox::run to reach RUNNING, up to timeout, and then for its daemon to answer, up to crate::session::DEFAULT_BOOTSTRAP_TIMEOUT.

run calls this itself unless RunRequest::wait was off. A launch that adopted an existing VM through its client token resumes it if it idle-suspended (#195); a fresh launch that reaches any terminal state first fails fast with stateReason (TRAP-8).

§Why RUNNING isn’t enough

RUNNING says the run hook answered, so the daemon is up and the token installed (STATE-2), but the endpoint’s proxy path commonly refuses a connection or two just after. The session is handed back once one health poll through it has answered, so a first exec after a launch doesn’t meet that window, on any surface (#254). A daemon that never answers fails this with ERR_TIMEOUT, and the lifecycle stays RUNNING: the VM is there and billing, and Sandbox::session can still wait for it.

Source

pub async fn wait_until_ready( &mut self, running_timeout: Duration, ) -> Result<&mut Session, Error>

Waits until the VM this sandbox addresses answers, whatever a launch still has to wait for: from PENDING, Sandbox::wait_until_running with running_timeout (RUNNING, then the daemon); from RUNNING, the daemon alone, up to crate::session::DEFAULT_BOOTSTRAP_TIMEOUT.

The shape for a process that adopted a VM another one launched without waiting: it can’t know which of the two waits is left, and the lifecycle Sandbox::adopt read from the service says. Any other lifecycle is refused, because a suspended or terminated VM isn’t on its way to answering.

Source

pub async fn adopt( control: ControlPlane, microvm_id: impl Into<String>, endpoint: impl Into<String>, agent_token: impl Into<String>, ) -> Result<Sandbox, Error>

A sandbox for a VM another process launched, rebuilt from its private record.

The durable-workflow shape: every step runs in a fresh process, and each one needs suspend, resume, and terminate, not just the exec and file surface Session::attach (microvms_core::prelude::SessionExt::attach) gives. The lifecycle is read from GetMicrovm rather than assumed, so every guard below starts from what the service reports.

§Bootstrap is counted, never repeated (STATE-3)

The VM was bootstrapped by the launch that created it, so an adopted sandbox refuses Sandbox::run and never sends a run-hook payload. A VM adopted while still PENDING finishes through Sandbox::wait_until_running, which counts the bootstrap once when the service reports RUNNING, the same as a launch this sandbox made itself.

§The suspended window (STATE-12)

The window comes from the idlePolicy GetMicrovm reports. A VM adopted while already SUSPENDED has no suspend time this client observed, so the local check has nothing to measure and the service answers the resume instead.

endpoint must agree with the service’s when the service reports one: a mismatch means the identifiers came from two different records. agent_token is the bearer credential the VM was launched with; it never appears in Debug or an error.

Source

pub fn name_record(&self, name: &str) -> Result<NameRecord, Error>

A crate::names::NameRecord for this VM, to register under name.

The egress posture is left unknown: the sandbox does not keep its launch request, and a record must not claim a network it cannot vouch for. The tunnel identity is kept when the sandbox holds one, launched with it or adopted from a record that carried it: a record without it can’t verify the VM later, which is what the pair is for (#263).

Source

pub async fn adopt_record( control: ControlPlane, record: NameRecord, ) -> Result<Sandbox, Error>

Sandbox::adopt from a crate::names::NameRecord kept in any store.

Keeps the record’s tunnel identity, so Sandbox::tunnel_identity and a later Sandbox::name_record carry it. A record holding one half of the pair, or a pair that doesn’t decode, is refused before any AWS call rather than adopted without it: the record claims a verifiable VM, and dropping the claim would hide that it’s broken.

Source

pub fn detach(&mut self) -> Result<Detached, Error>

Hands the VM off to another process and returns what that process needs to adopt it.

For a workflow whose steps run in different processes (a durable function’s launch step, then later steps that Sandbox::adopt): the launching process calls this instead of dropping the sandbox, which would warn that a live VM was abandoned. The VM keeps running and nothing is sent to AWS. From here on this sandbox is inert — its session is dropped and run, wait_until_running, suspend, resume, and terminate are refused — because the adopter now owns the lifecycle, and a second handle driving it would bypass the adopter’s guards.

Refused (Precondition) when there is no live VM to hand off: nothing launched yet, already torn down or terminated, or already detached.

Source

pub fn detached(&self) -> bool

Whether Sandbox::detach handed this sandbox’s VM to another process.

Source

pub fn adopted(&self) -> bool

Whether this sandbox was built by Sandbox::adopt rather than by its own launch.

Source

pub async fn suspend(&mut self) -> Result<(), Error>

Freezes the VM and waits for the platform to report it.

A freeze and restore rather than a stop and start: the guest keeps its memory, so the token, the filesystem, and every exec record survive. The one thing that does not is the guest’s view of time — it observes the whole suspension as a single jump, so any timeout, lease, or TLS session a running command holds expires at once on resume.

§STATE-5 is checked before the wire, not after

A suspend from anything but RUNNING is refused here with zero control-plane calls. That is the observable difference between this and a client that lets AWS answer, and it is what the test asserts on.

Source

pub async fn resume(&mut self) -> Result<&mut Session, Error>

Thaws the VM and returns a usable session.

§What is deliberately not re-delivered (STATE-7)

Nothing. No run-hook payload, no token, no bootstrap. The in-memory token survived the freeze, and re-delivering it would hit the daemon’s one-shot bootstrap and be refused — a 409 that reads like a broken VM.

§The window is checked first (STATE-12)

Before any wire call, because the answer is already known and calling costs the poll timeout to learn something worse. The falsification is a service reporting TERMINATED: without this check the resume burns the full deadline and then reports a state the client could have named at once.

§The proxy token is dropped (STATE-8)

Session::rebind invalidates it. The endpoint URL does not change across suspend/resume, so the rebind is usually a no-op on the URL — but a token minted against the pre-suspend instance may no longer validate, and that rejection reads exactly like a dead daemon.

Source

pub async fn terminate(&mut self, opts: TeardownOpts) -> TeardownReport

Tears down, best-effort, never erroring.

Order: VM, then image, then the log group last. The log group is last because the service can recreate a group deleted before its image — and see TeardownReport::undeleted for why this crate names it rather than deleting it.

Source§

impl Sandbox

This impl block contains no public items.

Warns about a live VM rather than tearing it down.

See the module docs: Drop cannot await, so a teardown here would either deadlock inside a runtime or race the process exit. The warning names the id, because the only useful thing a drop can do is tell whoever reads stderr what to go delete.

The warning goes through the plane’s Adapters::warn, which in production writes it to stderr.

Trait Implementations§

Source§

impl Debug for Sandbox

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Drop for Sandbox

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl SandboxExt for Sandbox

Source§

async fn new(region: Region) -> Result<Self, Error>

Resolves credentials for region and returns a sandbox with nothing launched. Read more
Source§

fn adopt_in( region: Region, microvm_id: impl Into<String>, endpoint: impl Into<String>, agent_token: impl Into<String>, port: Option<u16>, ) -> impl Future<Output = Result<Self, Error>> + Send

Sandbox::adopt over a plane resolved for region, on port when the image’s daemon listens somewhere other than the default. The bindings’ entry point.
Source§

async fn from_name( store: &dyn NameStore, name: &str, region: Option<Region>, port: Option<u16>, ) -> Result<Self, Error>

Adopts the VM registered as name in store: Sandbox::adopt from a record. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more