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 resume of an ended VM, and why teardown is explicit.

Implementations§

Source§

impl Sandbox

Source

pub fn with_control_plane(control: ControlPlane) -> Self

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 every wait here sleeps on, rather than a second one this type would own. Two clocks in one lifecycle is the trap the daemon’s turmoil tests record: a deadline measured on one clock while the poll loop sleeps on another reads the wrong elapsed time.

Source

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

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: SharedBackend) -> Self

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 and returns its session. By default it waits for RUNNING and for the daemon to answer; setting RunRequest::wait to false skips those readiness waits.

§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 run_cancellable( &mut self, request: RunRequest, cancellation: &RetryCancellation, ) -> Result<&mut Session, Error>

Like run, but stops retries when signaled. An in-flight request is allowed to finish so an accepted VM is recorded; cancellation never discards its identity.

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<Self, 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.

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<Self, 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 service decides, and a VM it ended is WindowClosed (STATE-12)

No resume is refused here for the time the VM has spent suspended. The service ends a VM the idle policy suspended once suspendedDurationSeconds passes, and it kept a VM this client suspended resumable 1283 s into a 60 s window (measured 2026-10-12, us-east-1, #597). A resume the service refuses because the VM ended, whatever ended it, is ErrorKind::WindowClosed (ControlPlane::resume), and this records the VM as terminated, so a second resume is refused here with no call (STATE-11). A VM the idle policy suspended may still be RUNNING here, so resume first reads its service state. It resumes from SUSPENDED and reports WindowClosed for an ended VM.

§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.

Trait Implementations§

Source§

impl Debug for Sandbox

Source§

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

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

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<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> 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.