feat(blaze): capture Firecracker checkpoints - #2473
Conversation
0df7f3e to
6f8dd79
Compare
|
@codex review Rebuilt as two commits on current main (includes merged #2472 and #2475): Firecracker checkpoint capture via /snapshot/create with version provenance frozen from the running VM, and Firecracker restore via /snapshot/load starting a bare VMM. Verified on real KVM with Firecracker v1.16.0; the restored owner carries no --config-file, proving a genuine snapshot load rather than a cold-boot fallback. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f8dd79fb6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
6f8dd79 to
51d580a
Compare
|
@codex review Fixed the P1: the captured network shape now travels with the request via BackendInstance::holds_network_slot and RestoreRequest::preserve_network, probed while the captured owner is still alive, so a restore of a networked sandbox creates a fresh slot and rebinds through network_overrides instead of referencing a deleted tap. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51d580a024
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
51d580a to
fa98793
Compare
|
@codex review Fixed the P2 as the second instance of one flaw: all three backend configuration fields a restore actually consumes (network shape, guest transport, console logging) now travel with the request via BackendInstance probes taken while the captured owner is still alive, instead of being rebuilt from defaults. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fa98793eef
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
fa98793 to
305be57
Compare
|
@codex review Fixed the snapshot deadline: control requests keep the short bound that catches a wedged VMM, while /snapshot/create and /snapshot/load use their own documented bound. This PR's own E3 run captured a 512 MiB guest in 29.04s against the old 30s budget, so the shipped 2G example policy could not have checkpointed. Split across both commits so the capture commit is independently correct. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 305be57c1c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
305be57 to
5a25cad
Compare
|
@codex review Fixed the console-log truncation that the previous fix exposed: the log is now reopened in append mode, so a restore keeps the history captured before it while rotation still bounds the file. Two tests pin both the retained-history and recording-disabled cases. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a25cad16b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
- 核查 Firecracker 检查点/恢复路径中的版本与二进制固定策略。
- 确认 Firecracker 版本缺失约束在捕获与 manifest 校验层次上的职责边界。
- 确保未来运维策略变化时,版本校验与错误信息保持一致。
🤖 Generated by Qoder • View workflow run
5a25cad to
7f34a6a
Compare
|
@codex review Closed the executable TOCTOU: the generic transaction pins the backend executable once before the capability check and the launch executes that same pinned file, so a binary replaced after preflight can no longer be discovered only after the original was killed. Re-verified on real hardware — the restored VMM runs from /proc/self/fd/N with /proc/pid/exe resolving to the intended binary, through the ip-netns-exec and unshare chain. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f34a6add9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
7f34a6a to
176a311
Compare
|
@codex review Both fixed. The pin is now a sealed memfd copy rather than a descriptor to the original inode — measured on the target host that the kernel only blocks an in-place rewrite while a process executes that inode, which stops holding exactly when a restore kills the runtime it replaces. The copy is named after its source so a running backend stays identifiable as /memfd:firecracker. stderr.log now appends alongside the console log, so a failed replacement can still be debugged from pre-restore diagnostics. Gate 14/14 and E3 re-run end to end. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 176a311521
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
176a311 to
8dfe9fc
Compare
8dfe9fc to
d6d6a76
Compare
|
@codex review Fixed: the executability check now names the already-opened descriptor through /proc rather than the path, so the file that is checked and the file that is sealed are the same one by construction. Checking the path would have let a replacement vouch for a non-executable source — the same defect class this change exists to close. Used the /proc form instead of AT_EMPTY_PATH to avoid a faccessat2 kernel-version branch, since the change already depends on /proc for the exec itself. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6d6a762ac
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
d6d6a76 to
96471e9
Compare
|
@codex review Fixed, and it corrected a claim I had made earlier: memory stopped being a don't-care for restore the moment the snapshot deadline depended on it. A restored owner now takes its guest memory size from the captured memory artifact — for a full snapshot that size is the guest memory it holds — so a restored large guest no longer inherits the default and its next checkpoint gets a deadline that matches the work. The load reads the same recorded value instead of stat-ing separately. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96471e9ab1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
96471e9 to
a018abf
Compare
|
@codex review Fixed the documentation gap, which also corrects a judgement I made early on: I treated this as having no user-facing surface because it adds no routes, but the support matrix is user-facing behaviour documentation. Both language guides now state the actual Firecracker capture and restore support plus the prerequisites — version pinning for restorability, monitor process replacement, host-shape preservation, retained diagnostics, and refusal before anything is stopped on version mismatch. Split so each commit carries the statement it makes true. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a018abf52a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
a018abf to
0191790
Compare
|
@codex review Fixed: sealing protects the copy but not the reading, so the pin now brackets the copy and refuses a source whose copied length, post-copy size, or modification time moved. Rejecting rather than retrying, since a refusal during preflight leaves the sandbox untouched. Noted in the reply that only the length arm has a deterministic test — my first attempt at a racing test was wrong and driving the real race would need the pin restructured — rather than implying fuller coverage. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0191790a73
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
0191790 to
a1bafa2
Compare
|
@codex review Fixed: measured on the target host that a same-length rewrite plus utimensat restores both size and mtime while ctime still moves, so the stability check now compares inode change time as well — the one indicator an ordinary writer cannot put back. This property is deterministically testable without the race, so it is pinned by a test rather than left to inspection. |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
a1bafa2 to
f29d6cb
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f29d6cbd43
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
This lets Firecracker-backed sandboxes use the checkpoint API that alibaba#2472 already exposes, by pausing the VM, writing full VM-state and guest-memory snapshots through the Firecracker API socket, and resuming the owned runtime. The owner resolves its version from the running VM rather than from the configured binary and freezes it, so a concurrent binary replacement cannot make a capture claim a version this VM never ran. Firecracker snapshot formats are tied to that exact version, so a record without one cannot be restored safely; capture therefore refuses a Firecracker sandbox whose owner reports no version, and the checkpoint manifest rejects the same shape. Firecracker writes snapshot files itself from inside a private mount namespace, so a capture names a scratch directory below the runtime directory the VM already owns and transfers the artifacts to the destinations the publisher chose. Scratch is reclaimed only when the outcome is known: a rejected request never wrote anything, while an unknown outcome may still have a live writer, so that scratch is retained for reconciliation and reclaimed by destroy or startup instead of being deleted underneath Firecracker. Snapshot requests get their own deadline, scaled by guest memory. A control request such as `/version` or a pause should answer immediately, so a short bound catches a wedged VMM, but a full snapshot moves the whole guest memory and its duration scales with memory size and storage speed. Sharing the short bound would report an unknown outcome and fail a capture that was still making progress: a 512 MiB guest already takes about 29 seconds on real hardware. Guest memory has no configured upper bound either, so no fixed replacement bound would hold — at that rate a 16 GiB guest outlives a 15-minute deadline. The deadline is therefore derived from the memory size against a conservative throughput floor, with a floor for fixed overhead, and exists only to catch a VMM that never answers. A VM that answers on its API socket but not for its version stays a usable sandbox and only loses checkpoint capture: the owner carries no capture context, so it reports no version, does not advertise the capability, and a later capture is refused before anything is paused. Treating the probe as fatal would turn a lost checkpoint capability into a failed sandbox creation. Only full capture is enabled. Restore stays unavailable until its adapter lands. The user guides previously stated that Firecracker does not advertise capture support, which this makes untrue, so both language versions are corrected. They also now state the operational consequence of freezing the version: a checkpoint can only be loaded back by the Firecracker build that took it, and capture refuses a Firecracker sandbox whose monitor reports no version, before the sandbox is paused, so a checkpoint without a recorded version cannot be produced. Tests cover the frozen version, a full snapshot over the API socket, scratch transfer through inherited descriptors, and the retained-scratch boundaries for rejected requests, uncleanable scratch, and disconnects. Closes alibaba#2470 Signed-off-by: Weisson <Weisson@linux.alibaba.com>
This lets the generic restore workflow from alibaba#2475 replace a running Firecracker-backed sandbox with the state committed to one of its checkpoints, which is the missing half a alibaba#2470 capture cannot exercise on its own. The adapter declares its restore identity from the configured binary version so the generic transaction only calls in when the checkpoint recorded that exact version. Version, backend, snapshot flavour and guest-transport shape are all checked before the current runtime is stopped, so a mismatch refuses the restore instead of tearing down a live VM. Start becomes checkpoint-aware. A cold start still writes the machine configuration file and boots vmlinux; a restore instead launches a bare VMM, because the snapshot carries the machine configuration the capture froze, and then hands the retained VM state and guest memory to Firecracker through `/snapshot/load` with `resume_vm: true`. The tap device and guest socket are recreated with fresh host names on every start, so the load overrides the names the snapshot recorded rather than requiring the previous host resources to still exist. The executable is pinned before anything is stopped. A restore reads the backend version during preflight and launches after the running sandbox is gone, so an executable changed in that window would only be noticed once the original could no longer be kept — turning a restore preflight could have refused without harm into a sandbox needing recovery. Holding a descriptor to the original file is not enough, because a descriptor pins the inode and not its contents; the kernel refuses an in-place rewrite only while some process executes that inode, which stops holding for the replaced sandbox's own binary once its runtime is killed. `PinnedExecutable` therefore copies the bytes into a sealed memory file, and refuses a source that did not hold still across that copy: sealing protects the destination, not the reading, so an in-place rewrite mid-copy would otherwise splice an old prefix onto new bytes and seal that permanently. Length and modification time cannot carry that check alone, because a same-length rewrite can restore both, so the comparison includes inode change time — the one indicator an ordinary writer cannot put back. That copy is executable in its own right, so the source's own execute permission is checked first: otherwise a restore would run a backend a cold start would refuse, quietly overriding an operator who withdrew it. A restored owner is sized by the captured memory image rather than by the reconstructed configuration: a restore writes no VM configuration to state guest memory, so reading it from there would freeze the default and leave a later capture of a restored large guest with the minimum deadline. Both the load and any subsequent capture are bounded from that one recorded size. The user guides previously stated that only the mock adapter restores checkpoints, which this makes untrue, so both language versions are corrected. They also state what an operator observes: the monitor process is replaced while the sandbox identity is not, the replacement keeps the captured host shape, earlier console and diagnostic output survives, and a version mismatch is refused before anything is stopped. The generic transaction pins once, hands the same pin to both the capability check and the launch, and the replacement executes the sealed copy rather than re-resolving the configured path. Because the console setting now survives, reopening the console log and the VMM diagnostics appends instead of truncating. A cold start owns a fresh runtime directory so nothing changes there, but a restore reuses the directory of the sandbox it replaces, and truncating would have erased the console history captured before the restore. Loading a snapshot shares the capture-side deadline rather than the short control bound, for the same reason: reading a large memory image back is throughput-bound, so a short bound would abandon a load that was still progressing. The capture-version probe stays fatal on this path, unlike a cold start which keeps running without capture: a restore must load the snapshot into a VM whose version was confirmed to match the one that captured it, and an unverified replacement is worse than a refused restore. Failure edges follow the two boundaries the trait already expresses. Everything before `spawn` uses `SpawnFailure::clean`: no VMM was started, no host resource was allocated. Once `spawn` returns, the load runs against the owned instance and any failure — compatibility rejection or `/snapshot/load` error — transfers the owner through `SpawnFailure::compensate_started` so the generic transaction can reconcile a runtime whose cleanup is still owned rather than leaking a started VMM. The captured host shape travels with the request rather than being rebuilt from defaults. `BackendInstance` gains `holds_network_slot` and `records_console_log`, which the generic transaction probes while the captured owner is still alive, because that owner's cleanup removes the host device the snapshot names. A restore consumes exactly three backend configuration fields: the network and guest-transport shapes name host devices the snapshot references, and the console setting decides whether guest output keeps being recorded; the remaining fields only feed the machine configuration a restore does not write. Rebuilding from defaults would have left a restored VM bound to a deleted tap after the running VM was already stopped, and would have silently stopped recording console output. Tests cover the load payload rebinding the tap and guest socket this start created, omitting overrides the sandbox does not own, and refusing a mismatched version, a missing version, a foreign backend, and a guest-transport shape the policy does not provide. Closes alibaba#2633 Signed-off-by: Weisson <Weisson@linux.alibaba.com>
f29d6cb to
3a2a169
Compare
|
@codex review Fixed the cold-start regression: a VM that answers on its API socket but not for its version now keeps running without checkpoint capture, instead of failing sandbox creation. A restore stays fatal on that path, since it must load a snapshot into a VM whose version was confirmed. Split across both commits so the capture commit degrades on its own and the restore commit adds the guard. Also replied to three earlier qoderai comments I had missed — my unreplied-comment check filtered on a single reviewer account, which is fixed. |
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
casparant
left a comment
There was a problem hiding this comment.
Reviewed against the baseline we have been applying since #1830.
Review baseline — all met:
- Unified motivation: clear Why grounded in the post-#2472/#2475 main — the checkpoint capture and restore workflows were backend-neutral and returned unsupported/501 for Firecracker; this PR supplies the real
/snapshot/createand/snapshot/loadtransport so Firecracker-backed sandboxes are no longer mock-only. Two commits, capture (Closes #2470) and restore (Closes #2633). - No caller-less code: every new pub item has a production call site —
FirecrackerInstanceimplements the #2472 capture contract (supports_checkpoint_capture/version/pause/snapshot/resume),FirecrackerSpawnerimplements the #2475 restore contract (restore_capability/restore), andPinnedExecutableis exercised in the restore preflight and launch paths; MockSpawner retains only its test-contract implementation. - Explicit boundary: only
Fullsnapshots are exposed (validate_restore_compatibilityrejects non-full); non-Firecracker backends stay fail-closed; #2476 remains the follow-up that this PR unblocks. No new HTTP routes. - Docs: bilingual user guides updated with the Firecracker capture/restore support matrix, exact-version requirement, monitor-process replacement, and preserved console/diagnostics behavior.
- Commits: two atomic commits mapped to their issues; CI 8/8 green; all inline threads carry replies and the key fixes (preserve_network, record_console_log, memory-scaled snapshot timeout, PinnedExecutable, cold-start version-probe degradation) are verified present in the final head.
- Naming:
restore/resume/load_snapshotconfined to the data plane; control-plane failure handling uses cleanup/compensate/journal vocabulary;dirtyappears only as the Firecrackertrack_dirty_pagespayload field.
Porting note (for the record):
The Firecracker orchestration (pause → snapshot create → resume; restore via bare VMM + /snapshot/load) is migrated from the Go PoC, but the Rust side is a productization redesign layered on the #2472/#2475 backend contracts — adding an exact-version compatibility gate, a pinned backend executable (sealed memfd with len/mtime/ctime comparison), and a clean-vs-compensate failure boundary that the PoC did not have.
One non-blocking note:
The functional boundary is complete but scattered across "No new HTTP routes", the closed issues, and the full-only restriction — a short explicit "Not in this PR" paragraph in the body would aid discoverability.
LGTM.
Why
Blaze already exposes checkpoint capture (#2472) and checkpoint restore (#2475),
but both were backend-neutral: on the only real production backend the routes
refused the request, because
FirecrackerInstanceimplemented neither pause andsnapshot nor a restore adapter. Capture returned
501, and restore reported anunsupported backend.
This PR implements both halves for Firecracker, which is what turns those two
merged APIs from mock-only into usable on a real VM backend. It also unblocks
#2476, whose hibernation flow needs the same restore adapter.
What changed
No new HTTP routes. Two commits, each independently buildable and reviewable.
Commit 1 — capture (
Closes #2470)Adds the Firecracker HTTP-over-UDS API client and implements
pause,resume,snapshot,versionandsupports_checkpoint_captureon the owner. A capturepauses the VM, writes full VM-state and guest-memory snapshots, and resumes the
runtime.
Version provenance. The owner resolves its version from the running VM
(
GET /version) instead of from the configured binary, and freezes it. Aconcurrent binary replacement therefore cannot make a capture claim a version
this VM never ran. Because Firecracker snapshot formats are tied to that exact
version, a record without one cannot be restored safely, so capture refuses a
Firecracker sandbox whose owner reports no version and the checkpoint manifest
rejects the same shape. Both user guides state that invariant explicitly, including
that the refusal happens before the sandbox is paused.
Losing the version costs only the capability, not the sandbox: a cold start whose VM
answers on its API socket but not for its version keeps running without capture, so a
checkpoint capability failure is not turned into a failed sandbox creation. A restore
stays fatal there, because it must load a snapshot into a VM whose version was
confirmed to match the one that captured it.
Snapshot deadline. A control request must answer immediately, so a short bound
catches a wedged VMM; snapshot work is throughput-bound on guest memory size and
storage speed. Sharing one value meant a capture that was still progressing was
reported as an unknown outcome — the E3 run below captured a 512 MiB guest in
29.04 s against a 30 s budget, so the example policy's
memory = "2G"could nothave checkpointed. Guest memory has no configured upper bound either, so no fixed
replacement would hold: at that measured rate a 16 GiB guest needs about 15.5
minutes. The deadline is therefore
max(bytes / 4 MiB/s, 120 s)and exists onlyto catch a VMM that never answers. A cold start sizes it from the configured
memory; a restored owner sizes it from the captured memory image, whose size is the
guest memory it holds, because a restore writes no VM configuration to read it from
and defaulting it would leave a later capture of a restored large guest with the
minimum deadline.
Scratch ownership. Firecracker writes snapshot files itself from inside a
private mount namespace, so a capture names a scratch directory below the
runtime directory the VM already owns, then transfers the artifacts to the
destinations the publisher chose. Scratch is reclaimed only when the outcome is
known: a rejected request never wrote anything, while an unknown outcome may
still have a live writer, so that scratch is retained for reconciliation and
reclaimed by destroy or startup instead of being deleted underneath Firecracker.
Commit 2 — restore (
Closes #2633)Implements
restore_capabilityandrestore, and makesstartcheckpoint-aware.A cold start still writes the machine configuration and boots
vmlinux. Arestore instead launches a bare VMM — the snapshot carries the machine
configuration the capture froze — and hands the retained VM state and guest
memory to Firecracker through
PUT /snapshot/loadwithresume_vm: true. Thetap device and guest socket are recreated with fresh host names on every start,
so the load overrides the names the snapshot recorded rather than requiring the
previous host resources to still exist.
Version, backend, snapshot flavour and guest-transport shape are all checked
before the current runtime is stopped, so a mismatch refuses the restore instead
of tearing down a live VM.
Failure boundaries use the two the trait already expresses. Everything
before
spawnreportsSpawnFailure::clean: no VMM was started and no hostresource was allocated. Once
spawnreturns, the load runs against the ownedinstance, and any failure — compatibility rejection or
/snapshot/loaderror —transfers the owner through
SpawnFailure::compensate_started, so the generictransaction reconciles a runtime whose cleanup is still owned rather than leaking
a started VMM.
The captured host shape travels with the request rather than being guessed:
BackendInstance::holds_network_slotandrecords_console_logare probed by thegeneric transaction while the captured owner is still alive, and travel in
RestoreRequestbesideexpose_guest_socket. A restore consumes exactly threebackend configuration fields — the network and guest-transport shapes name host
devices the snapshot references, and the console setting decides whether guest
output keeps being recorded.
boot_args,vcpusandmemoryare read only bythe machine configuration a restore does not write, so rebuilding those from
defaults is harmless while rebuilding the other three was not. Because the console
setting now survives, the log is reopened in append mode: a restore reuses the
directory of the sandbox it replaces, and truncating would have erased the console
history captured before the restore.
Executable pinning. Preflight reads the backend version, and the launch that
follows happens after the running sandbox is stopped. An executable changed in that
window was only caught by the post-spawn version comparison, once the original
could no longer be kept, so a restore preflight could have refused without harm
instead left the sandbox needing recovery. A descriptor alone does not close this:
it pins the inode, not the contents, and the kernel refuses an in-place rewrite
only while some process executes that inode — which stops holding for the replaced
sandbox's own binary once its runtime is killed.
PinnedExecutablethereforecopies the bytes into a sealed memory file, opened once by the generic transaction
and handed to both the capability check and the launch. Sealing makes the copy
immutable but does not make reading the source atomic, so the pin also refuses a
source that did not hold still across the copy — a spliced image could still answer
--versionand fail only once the sandbox was already stopped. Length andmodification time cannot carry that alone, since a same-length rewrite can restore
both, so the comparison includes inode change time, which an ordinary writer cannot
put back. That copy is executable in
its own right, so the executability of the file it copies is checked first —
against that opened descriptor rather than the path, so a replacement cannot vouch
for a source it is not. Otherwise a restore would run a backend a cold start would
refuse, overriding an operator who withdrew execute permission, and only after the
current VM was already stopped. A backend that runs no
program of its own carries no configured path, so the pin is optional and an
adapter that needs an executable refuses its absence itself.
Two operability consequences, both intentional. A restored VMM shows
/proc/self/fd/Nasargv[0], so process listings match on--id fc-<uuid>rather than the binary name. And
/proc/<pid>/exereports/memfd:firecracker (deleted): the copy is named after its source so it staysidentifiable, while the
memfd:prefix says plainly that the running image is asealed copy the on-disk path can no longer change.
User guides. The support matrix said Firecracker could not capture and that
only the mock adapter restores, which this makes untrue, so both language versions
are corrected — each statement in the commit that makes it true. They also record
the prerequisites an operator needs: a checkpoint is restorable only by the
Firecracker build that took it, so that version must stay installed; a restore
replaces the monitor process while keeping the sandbox identity; the replacement
keeps the captured host shape; earlier console and diagnostic output survives; and a
version mismatch is refused before anything is stopped.
Deliberately not included
The earlier draft branch parsed a payload directory with its own manifest and
file-set validation. That layer is dropped: the generic restore transaction from
#2475 already verified the checkpoint, its parent chain and every artifact hash,
and passes the artifact paths and expected identity directly. Re-validating them
in the adapter would duplicate #2475 rather than adapt to it.
Scope
db0e4654e14808d2398c813719e4742567e88954(main, includes merged feat(blaze): capture sandbox checkpoints #2472 and feat(blaze): restore sandbox checkpoints #2475)`b8f54ce87b4da84860f80b98bb54378579509c8942e9769730427fb6a5f8ea6a`
/v1/instancespathsThis PR does not re-carry the 22 accumulated commits of the previous draft
branch. Its history is rebuilt on current
main.Validation
Linux gate — 14/14 pass
An uninterrupted Linux x86_64 run on Rust 1.88.0 completed for the tree above.
cargo fmt --all -- --checkcargo metadata(default / all-features)cargo build --workspace --all-targets(default / all-features)cargo clippy --workspace --all-targets -- -D warnings(default / all-features)cargo test --workspace -- --test-threads=1cargo test --workspace --all-features -- --test-threads=1cargo doc --workspace --no-deps(default / all-features)-D warningsscripts/docs-lint.sh/docs-link-check.py/git diff --check38 Firecracker tests cover the frozen version, a full snapshot over the API
socket, scratch transfer through inherited descriptors, the retained-scratch
boundaries for rejected requests and uncleanable scratch, transport classification
of disconnects, timeouts and oversized responses, the load payload rebinding the
tap and guest socket this start created, and refusal of a mismatched version, a
missing version, a foreign backend, and an unavailable guest transport.
Real Firecracker on KVM — E3
Bare-metal host,
/dev/kvmavailable, Firecracker v1.16.0, realvmlinuxand 482 MiB
rootfs.ext4.state: running, realfirecrackerprocess startedrunning → paused → checkpointed → runningvmstate.snap13 768 B,memory.snap512 753 664 B,rootfs.snap482 344 960 B,metadata.json"backend_version": "Firecracker v1.16.0"in the manifest.firecracker-checkpoint-*left after a successful captureGET .../checkpointsreports the entry asis_headandon_head_chainrunning → restoring → running, response"restored": true848480 → 849090--config-file, only--api-sockand--id, proving a bare VMM loaded the snapshot instead of falling back to a cold bootlast_checkpointThe
--config-filecheck is the decisive one: a cold start always passes amachine configuration file, so its absence on the restored owner is what
distinguishes a genuine
/snapshot/loadfrom a silent cold-boot fallback.Issues
Closes #2470— capture half, already scoped for capture only.Closes #2633— restore half, filed as the companion issue so each commit mapsto exactly one issue, mirroring how the generic side splits [blaze] feat: capture and list sandbox checkpoints #2444 (capture) and
[blaze] feat: restore sandboxes from checkpoints #2463 (restore).
Remaining dependency
This PR stays a draft until a maintainer confirms the E3 evidence above is
sufficient for the phase-3 acceptance gate. Once it merges, checkpoint capture and
restore become usable on Firecracker, and #2476 can complete its own real-backend
hibernate–restart–resume validation.
Rollback
Revert both commits. Firecracker returns to advertising no capture and no restore
capability, so the generic routes go back to reporting an unsupported backend.
Checkpoints captured by this code remain on disk and are not read by the reverted
build.