feat(blaze): restore sandbox checkpoints - #2475
Conversation
1f3b685 to
06a35b2
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06a35b2bf1
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
06a35b2 to
c65dc0f
Compare
|
@codex review This PR adds the backend-neutral |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex review |
c65dc0f to
4257e73
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c65dc0f3c3
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Backend-neutral sandbox checkpoint restore: adds |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
4257e73 to
2be88b7
Compare
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2be88b74a3
ℹ️ 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".
2be88b7 to
3c1dc73
Compare
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
3c1dc73 to
7c52ad3
Compare
|
@codex review |
871baac to
8bfde2c
Compare
|
@codex review Docs-only follow-up: the restore "keeps running" guarantee is now limited to failures strictly before the daemon begins stopping the old backend; a stop-step failure (kill error) and everything after is documented as RecoveryRequired, aligned across all four docs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8bfde2c8ba
ℹ️ 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".
8bfde2c to
5c7d2f2
Compare
|
@codex review Docs-only follow-up: propagated the restore failure boundary to lifecycle-state-consistency.md/_zh.md, and updated src/blaze/AGENTS.md to describe the lifecycle as 10 states with the Running → Restoring → Running branch. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5c7d2f2c67
ℹ️ 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".
Add the sandbox rollback endpoint and the backend-neutral restore contract needed to replace a running sandbox from a verified full checkpoint. Journal lifecycle ownership, stage and reconcile file-provider root filesystem replacement, preserve runtime-directory ownership across replacement startup, and fail unsupported backend adapters before mutation. Document the route, capability checks, durable ordering, failure boundaries, and checkpoint-history semantics in the English and Chinese operator documentation. Signed-off-by: Weisson <Weisson@linux.alibaba.com>
5c7d2f2 to
242120c
Compare
|
@codex review Docs-only follow-up: updated the checkpoint-section introductions in lifecycle-state-consistency.md/_zh.md to describe three routes (capture, history, restore) and the restore journal/lifecycle contract, matching the section body. |
|
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". |
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 main — Blaze can capture and list checkpoints but cannot yet restore a sandbox from one; this PR adds
POST /v1/sandboxes/{id}/rollback/{checkpoint_id}with full-lifecycle journaling and transactional storage replacement. - No caller-less code: every new pub item (
RestoreCapability,RestoreRequest,StorageRestoreTransactionand its five trait methods,begin_restore_operation,advance_restore_phase,read_head_id,BackendSpawner::restore_capability/restore) has a production call site in the restore worker or storage provider; test-only spawners (GuestMockSpawner,TransportDroppingRestoreSpawner) are correctly gated behind#[cfg(test)]. - Explicit boundary: Firecracker-specific restore transport deferred to #2473; checkpoint deletion/pruning not provided; no
/v1/instancescompatibility route added — all stated in the PR body. - Docs: bilingual user guides, READMEs, and lifecycle-state-consistency / storage-artifact-synchronization design docs updated; AGENTS.md state machine description updated (9→10 states with
Restoring). - Commits: single atomic commit after multiple revision iterations; CI 8/8 green; all 10 inline threads carry exactly one substantive reply each and fixes are verified present in the final head
242120cde70b. - Naming:
restorecorrectly confined to the data-plane checkpoint recovery path; control plane uses journal/phase/operation vocabulary; no misuse of reserved word families.
One non-blocking note:
The file_provider/restore.rs at ~1650 lines is the densest single-file addition in the series — the internal structure (stage/activate/commit/abort/reconcile) is sound, but a future crate-level split would improve navigability.
LGTM.
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. 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. 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>
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. 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. `BackendInstance` gains `holds_network_slot`, which the generic transaction probes while the captured owner is still alive, because that owner's cleanup removes the host device the snapshot names. Reconstructing the spawn configuration from defaults would have dropped networking, leaving a restored VM bound to a deleted tap after the running VM had already been stopped. 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>
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. 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>
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. 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. 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>
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. Because the console setting now survives, reopening the console log 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. 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>
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 replaced 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. `PinnedExecutable` holds the file open, the generic transaction opens it once and hands the same pin to both the capability check and the launch, and the replacement executes that pinned file rather than re-resolving the configured path. Because the console setting now survives, reopening the console log 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. 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>
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. 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. 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>
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. 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 load's deadline follows the captured memory image's size, the same way a capture's follows the configured memory. 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. 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>
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. 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 load's deadline follows the captured memory image's size, the same way a capture's follows the configured memory. 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. 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>
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. 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 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. 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>
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. 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. 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>
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. 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. 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>
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. 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>
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. 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>
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>
* feat(blaze): capture Firecracker checkpoints This lets Firecracker-backed sandboxes use the checkpoint API that #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 #2470 Signed-off-by: Weisson <Weisson@linux.alibaba.com> * feat(blaze): restore Firecracker checkpoints This lets the generic restore workflow from #2475 replace a running Firecracker-backed sandbox with the state committed to one of its checkpoints, which is the missing half a #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 #2633 Signed-off-by: Weisson <Weisson@linux.alibaba.com> --------- Signed-off-by: Weisson <Weisson@linux.alibaba.com>
Why
Blaze can capture and list committed checkpoints, but it cannot yet replace a
running sandbox with a selected checkpoint through the public sandbox API. The
replacement must keep backend, storage, lifecycle, and checkpoint history in
agreement even when a request is cancelled or the daemon stops partway through
the operation.
What changed
POST /v1/sandboxes/{id}/rollback/{checkpoint_id}.image, backend identity, backend version, and snapshot kind before mutation.
unsupported combinations return
501before stopping the current runtime.mock backend implements it for end-to-end validation. Firecracker-specific
restore transport remains outside this pull request and will be delivered
with Firecracker checkpoint support in feat(blaze): capture Firecracker checkpoints #2473.
filesystem, retains the predecessor, activates without replacement races,
and can either commit or roll back after restart.
detached supervisor after client cancellation, and preserve recovery state
whenever the result cannot be proven.
HEADonly after the replacement backend is live and ready.Restoring an older checkpoint changes only
HEAD; it does not delete orrewrite later checkpoint history.
and rollback procedure in the English and Chinese README, user guide, and
component design documents.
No
/v1/instancescompatibility route is added.Related issue
Closes #2463
User / Agent impact
Users and agents can select a committed checkpoint and request replacement of a
running sandbox. Success returns the sandbox identifier, selected checkpoint,
restored: true, and the resultingrunningstate.The generic path currently succeeds with the built-in mock backend and file
storage provider. Other backend or provider combinations fail with
501beforeruntime mutation until they implement the explicit restore contract.
Risk and compatibility
This adds one HTTP endpoint, new backend and storage capability contracts, new
restore lifecycle journal phases, and file-provider transaction artifacts.
Existing implementations remain fail-closed because both restore capabilities
default to unsupported.
Checkpoint directories and artifacts are retained by open descriptors during
verification and replacement. Storage publication uses private staging files,
identity checks, no-follow opens, durable rename boundaries, and a journal that
restart reconciliation can finish or abort.
Validation
Exact candidate:
7a8005874e078455e9acde0f4f6981839919489d(main, includes merged feat(blaze): capture sandbox checkpoints #2472)242120cde70b584788863534567429bc5d54d08ae2a4f508ff0a9672ccd43a08818c2c511923198eCompleted for this exact commit:
/v1/instancesroute, andno Firecracker restore implementation.
An uninterrupted Linux x86_64 run on Rust 1.88.0 completed for the tree above.
It used a fresh source directory, a dedicated Cargo home, and an initially
empty task-exclusive target directory for each stage. Every Cargo stage ran
with
--locked --offline.cargo fmt --all -- --checkcargo metadata, default and all featurescargo build --workspace --all-targets, default and all featurescargo clippy --workspace --all-targets -- -D warnings, default and all featurescargo test --workspace -- --test-threads=1cargo test --workspace --all-features -- --test-threads=1RUSTDOCFLAGS=-D warnings cargo doc --workspace --no-deps, default and all featuresscripts/docs-lint.shscripts/docs-link-check.pygit diff --checkThe all-feature run covers 40 focused restore tests. They prove the rollback
route end to end on the built-in mock backend,
501rejection before any stateor resource change, per-stage cancellation and failure compensation, staging
that keeps the live root filesystem intact, predecessor retention through
activation, transaction reconciliation after an interrupted boundary,
HEADmovement without capture-history rewrite, and restore journal phase rules.
Real virtual-machine backends are unchanged: they still reject restore with
501until they implement the contract.This commit is directly based on the merged #2472 in
main(
7a8005874e078455e9acde0f4f6981839919489d). The PR consists of one commit.Documentation and rollback
The English and Chinese Blaze README, runtime user guide, lifecycle consistency
design, and storage synchronization design describe the new API and recovery
contract. No changelog entry is added because Blaze has not been released.
To roll back, stop the Blaze daemon and verify that no sandbox has an unfinished
restore operation or
RecoveryRequiredstate. Revert this commit together withits API, lifecycle, backend, storage, and documentation changes. Preserve any
restore transaction artifacts for operator inspection until the current binary
has reconciled or explicitly cleaned them.