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
impl Sandbox
Sourcepub fn with_control_plane(control: ControlPlane) -> Sandbox
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 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.
Sourcepub fn with_build_services(self, services: Arc<dyn BuildServices>) -> Sandbox
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.
Sourcepub fn with_session_backend(self, backend: Arc<dyn HttpBackend>) -> Sandbox
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.
Sourcepub fn token_installed(&self) -> bool
pub fn token_installed(&self) -> bool
Whether the agent token has been installed (STATE-2).
Sourcepub fn image_exists(&self) -> bool
pub fn image_exists(&self) -> bool
Whether an image is recorded as existing (STATE-1).
Sourcepub fn was_terminated(&self) -> bool
pub fn was_terminated(&self) -> bool
Whether this VM was ever terminated (STATE-11).
Sourcepub fn bootstrap_count(&self) -> u32
pub fn bootstrap_count(&self) -> u32
How many times the token has been installed. Never above one (STATE-3).
Sourcepub fn launch_image_arn(&self) -> Option<&str>
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).
Sourcepub fn suspended_window(&self) -> Option<Duration>
pub fn suspended_window(&self) -> Option<Duration>
The suspended window this sandbox asked for at launch, once it has launched.
Sourcepub fn watch_lifecycle(&self) -> Receiver<Lifecycle>
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.
Sourcepub fn idle_window(&self) -> Option<Duration>
pub fn idle_window(&self) -> Option<Duration>
The idle window this sandbox asked for at launch, once it has launched.
Sourcepub fn tunnel_identity(&self) -> Option<&TunnelIdentity>
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.
Sourcepub fn egress_posture(&self) -> EgressPosture
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.
Sourcepub fn port(&self) -> u16
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.
Sourcepub async fn build_image(
&mut self,
request: CreateImageRequest,
) -> Result<&Image, Error>
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.
Sourcepub async fn ensure_image(
&mut self,
request: EnsureImageRequest,
) -> Result<EnsuredImage, Error>
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.
Sourcepub fn build_artifact_for(
&self,
request: &CreateImageRequest,
) -> Result<Vec<u8>, Error>
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.
Sourcepub async fn upload_artifact_for(
&mut self,
request: &CreateImageRequest,
) -> Result<(), Error>
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.
Sourcepub fn preflight(&self, request: &CreateImageRequest) -> Result<(), Error>
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.
Sourcepub async fn resolve_image_arn(&self, identifier: &str) -> Result<String, Error>
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.
Sourcepub async fn find_image_by_name(
&self,
name: &str,
) -> Result<Option<MicrovmImageSummaryWire>, Error>
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.
Sourcepub fn artifact_content_hash_for(&self, request: &CreateImageRequest) -> String
pub fn artifact_content_hash_for(&self, request: &CreateImageRequest) -> String
The content hash build --reuse keys an image name to. Local; zero calls.
Sourcepub fn dockerfile_for(&self, request: &CreateImageRequest) -> String
pub fn dockerfile_for(&self, request: &CreateImageRequest) -> String
The Dockerfile request builds with; see ControlPlane::dockerfile_for. Local;
zero calls.
Sourcepub async fn managed_base_versions(
&self,
base_image_arn: &str,
) -> Result<Vec<ManagedMicrovmImageVersionWire>, Error>
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.
Sourcepub async fn image_version(
&self,
identifier: &str,
version: &str,
) -> Result<MicrovmImageVersionSummaryWire, Error>
pub async fn image_version( &self, identifier: &str, version: &str, ) -> Result<MicrovmImageVersionSummaryWire, Error>
One version’s configuration, state, and availability status. Read-only.
Sourcepub async fn run(&mut self, request: RunRequest) -> Result<&mut Session, Error>
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.
Sourcepub async fn run_cancellable(
&mut self,
request: RunRequest,
cancellation: &RetryCancellation,
) -> Result<&mut Session, Error>
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.
Sourcepub async fn wait_until_running(
&mut self,
timeout: Duration,
) -> Result<&mut Session, Error>
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.
Sourcepub async fn wait_until_ready(
&mut self,
running_timeout: Duration,
) -> Result<&mut Session, Error>
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.
Sourcepub async fn adopt(
control: ControlPlane,
microvm_id: impl Into<String>,
endpoint: impl Into<String>,
agent_token: impl Into<String>,
) -> Result<Sandbox, Error>
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.
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.
Sourcepub fn name_record(&self, name: &str) -> Result<NameRecord, Error>
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).
Sourcepub async fn adopt_record(
control: ControlPlane,
record: NameRecord,
) -> Result<Sandbox, Error>
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.
Sourcepub fn detach(&mut self) -> Result<Detached, Error>
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.
Sourcepub fn detached(&self) -> bool
pub fn detached(&self) -> bool
Whether Sandbox::detach handed this sandbox’s VM to another process.
Sourcepub fn adopted(&self) -> bool
pub fn adopted(&self) -> bool
Whether this sandbox was built by Sandbox::adopt rather than by its own launch.
Sourcepub async fn suspend(&mut self) -> Result<(), Error>
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.
Sourcepub async fn resume(&mut self) -> Result<&mut Session, Error>
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.
Sourcepub async fn terminate(&mut self, opts: TeardownOpts) -> TeardownReport
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.
impl Sandbox
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 SandboxExt for Sandbox
impl SandboxExt for Sandbox
Source§async fn new(region: Region) -> Result<Self, Error>
async fn new(region: Region) -> Result<Self, Error>
region and returns a sandbox with nothing launched. Read moreSource§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
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.Auto Trait Implementations§
impl !RefUnwindSafe for Sandbox
impl !UnwindSafe for Sandbox
impl Freeze for Sandbox
impl Send for Sandbox
impl Sync for Sandbox
impl Unpin for Sandbox
impl UnsafeUnpin for Sandbox
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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