Skip to content

Enhance sandbox pause/resume#1149

Open
ryanzhang-oss wants to merge 6 commits into
opensandbox-group:mainfrom
ryanzhang-oss:copilot/worktree-2026-06-27T20-56-53
Open

Enhance sandbox pause/resume#1149
ryanzhang-oss wants to merge 6 commits into
opensandbox-group:mainfrom
ryanzhang-oss:copilot/worktree-2026-06-27T20-56-53

Conversation

@ryanzhang-oss

@ryanzhang-oss ryanzhang-oss commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

This pull request added OSEP-0015 for "Spec-Driven Pod Snapshot for Pause and Resume" with status "draft" and date "2026-06-27".

Testing

  • Not run (OSEP proposal)
  • Unit tests
  • Integration tests
  • e2e / manual verification

Breaking Changes

  • None
  • Yes (describe impact and migration path)

Checklist

  • Linked Issue or clearly described motivation
  • Added/updated docs (if needed)
  • Added/updated tests (if needed)
  • Security impact considered
  • Backward compatibility considered

- Added OSEP-0013: Isolated Execution API (implementing, due 2026-06-23)
- Added OSEP-0014: Multi-Tenancy Support for Kubernetes Runtime (draft, due 2026-05-07)
- Added OSEP-0015: Spec-Driven Pod Snapshot for Pause and Resume (draft, due 2026-06-27)
Copilot AI review requested due to automatic review settings June 29, 2026 18:58
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ This PR has no labels. Please add one based on the changes.

Changed directories: oseps.

📋 Recommended labels (based on changed files):

  • documentation ⬅️

Other available labels:

  • bug - Something isn't working
  • dependencies - Pull requests that update a dependency file
  • feature - New feature or request
  • packages - Changes for package, image and configuration

💡 Tip: Use feature for new functionality or improvements, bug for fixes.

cc @ryanzhang-oss

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the OSEP index and introduces a new enhancement proposal documenting a spec-driven Kubernetes pause/resume design using inline snapshot state on BatchSandbox plus new SnapshotClass/SnapshotClaim CRDs.

Changes:

  • Add OSEP-0013/0014/0015 entries to oseps/README.md.
  • Add new proposal document oseps/0015-pod-snapshot.md describing pod snapshot-based pause/resume semantics and APIs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
oseps/README.md Extends the OSEP index table with entries for OSEP-0013 through OSEP-0015.
oseps/0015-pod-snapshot.md Adds the full OSEP-0015 draft describing the proposed pause/resume and snapshot CRD model.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +301 to +304
routes in `server/opensandbox_server/api/lifecycle.py` currently accept only the
path parameter and `X-Request-ID`. This OSEP updates those definitions to accept
an optional `PauseSandboxRequest` body. Omitting the body, sending `{}`, or using
old SDKs remains valid:
Comment thread oseps/0015-pod-snapshot.md Outdated
|---|---|---|---|---|
| `Stop` | Delete the Pod and release compute. | Lost unless on a retained PVC (OSEP-0003). | Lost | Supported (no snapshot, no `SnapshotClass` required) |
| `Freeze` | Keep the Pod, freeze container cgroups. | Kept | Kept (in node RAM) | Reserved (Kubernetes freeze is future work) |
| `Hibernate` | Capture full pod state via the snapshot Job, then delete the Pod. | Persisted as checkpoint artifact | Persisted as checkpoint artifact | **Implemented** (replaces the OSEP-0008 path) |
Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated
Comment on lines +980 to +982
This revision implements one snapshot mechanism: a same-node full-pod-state
snapshot Job. The `type` enum is the extension point for future backends without
changing the `BatchSandbox`

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e3285b20c

ℹ️ 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".

Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md
Comment on lines +1117 to +1119
9. Ctrl status.snapshot.phase: Pending -> Committing -> Ready,
artifacts[] populated
10. Ctrl Delete the Pod, status.phase = Paused.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recheck the source Pod UID before deleting

In clusters where the sandbox pod is evicted or recreated while the checkpoint job is running, this flow can record an artifact for one pod and then delete/report Paused for a replacement pod with the same name. Since the proposal records sourcePodUID, require the controller to compare that UID with the current live pod before deletion and fail closed if it changed, otherwise resume can restore stale state and lose writes from the replacement pod.

Useful? React with 👍 / 👎.

Comment thread oseps/0015-pod-snapshot.md Outdated
Comment on lines +1697 to +1700
When no `SnapshotClass` is referenced by that claim and none is annotated
default, the controller synthesizes an implicit default class from the legacy
startup flags (`--snapshot-registry`,
`--snapshot-registry-insecure`) so existing clusters keep working with zero

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Migrate registry secret settings too

Existing OSEP-0008 deployments commonly require --snapshot-push-secret and --resume-pull-secret (the controller flags and pause-resume guide both document them), but this zero-config migration only synthesizes defaults from --snapshot-registry and --snapshot-registry-insecure while the new design forbids Kubernetes Secrets. Authenticated registries will stop working after rollout unless the migration preserves those credentials somehow or the proposal removes the zero-config compatibility claim.

Useful? React with 👍 / 👎.

Comment thread oseps/0015-pod-snapshot.md Outdated
Comment on lines +523 to +534
metadata:
name: sandbox-abc123-pause-20260627
namespace: default
labels:
opensandbox.io/sandbox-id: sandbox-abc123
opensandbox.io/generated-from-template: tenant-s3
spec:
snapshotClassName: s3
parameters:
region: us-west-2
bucket: opensandbox-snapshots
prefix: tenants/default/sandbox-abc123/pauses/20260627

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make generated claim names unique per pause

When the same sandbox pauses from the same template more than once on the same day, this generated claim name and prefix collide with the previous pause. That conflicts with the design's rule that per-pause values must create a new claim instead of mutating a shared one, and with the e2e expectation that two template pauses produce distinct claims/prefixes; include a generation, timestamp with sufficient granularity, or nonce in both the claim name and default prefix.

Useful? React with 👍 / 👎.

Comment thread oseps/0015-pod-snapshot.md Outdated
Comment on lines +1198 to +1209
The implementation updates these source definitions:

- `specs/sandbox-lifecycle.yml`: add optional `requestBody` for
`/sandboxes/{sandboxId}/pause` that references `PauseSandboxRequest`; keep
`/resume` bodyless in this revision.
- `server/opensandbox_server/api/schema.py`: add Pydantic models for
`PauseSandboxRequest`, `SnapshotPauseRequest`, and
inline/generated claim options.
- `server/opensandbox_server/api/lifecycle.py`: accept
`body: PauseSandboxRequest | None = Body(None)` on `pause_sandbox`.
- Kubernetes provider/runtime code: materialize the selected/generated
`SnapshotClaim`, then patch the `BatchSandbox`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include SDK updates for the new pause body

This implementation plan updates the OpenAPI spec, FastAPI schema, route, and Kubernetes provider, but omits SDK interfaces. Current supported SDKs expose PauseSandbox(id) and send no request body, so advanced callers still cannot choose Stop, a claim, or a template through the public clients even though the proposal says new clients can opt in; add generated/handwritten SDK model and method updates to the required source changes.

Useful? React with 👍 / 👎.

@ryanzhang-oss ryanzhang-oss marked this pull request as draft June 29, 2026 19:56
@ryanzhang-oss ryanzhang-oss force-pushed the copilot/worktree-2026-06-27T20-56-53 branch from 2e3285b to 496889b Compare July 1, 2026 00:36
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 1, 2026
@ryanzhang-oss ryanzhang-oss force-pushed the copilot/worktree-2026-06-27T20-56-53 branch from 496889b to e91cd5a Compare July 1, 2026 00:49
@hittyt hittyt added the OSEP label Jul 1, 2026
@fengcone

fengcone commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

This is useful as a future Hibernate/checkpoint + pluggable snapshot backend design, but the motivation should not frame it as fixing a current server-side SandboxSnapshot orchestration problem. The current pause/resume guide documents the server as patching BatchSandbox.spec.pause=true/false; the BatchSandbox controller then creates/reads the internal SandboxSnapshot, and registry plus push/pull secrets are controller-level configuration. Please update the motivation and OSEP-0008 comparison to use that current flow and implementation as the baseline, then justify why replacing the controller-owned internal snapshot path with inline BatchSandbox snapshot state is necessary.

Please also preserve the current pool-mode pause/resume behavior. Sandboxes created with extensions.poolRef are paused by detaching from the pool: the controller solidifies the allocated pool Pod into a concrete spec.template, clears spec.poolRef, releases the pooled allocation, and resumes later from that solidified template. If the new inline path becomes the default, it should either keep this detach-from-pool flow or explicitly leave pool-backed sandboxes on the legacy path; otherwise existing pool-backed pause/resume support regresses.

@ryanzhang-oss ryanzhang-oss force-pushed the copilot/worktree-2026-06-27T20-56-53 branch from e91cd5a to 1233df6 Compare July 2, 2026 00:32
// Snapshot references the current SandboxSnapshot result for the latest pause.
// The full per-container artifact detail lives on that object.
// +optional
Snapshot *SnapshotStatusRef `json:"snapshot,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description of SnapshotStatusRef here is inconsistent with the description in the Kubernetes Resource Overview section.

Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated
|---|---|---|---|---|
| `Stop` | Delete the Pod and release compute. | Lost unless on a retained PVC (OSEP-0003). | Lost | Supported (no snapshot, no `SandboxSnapshotClass` required) |
| `Freeze` | Keep the Pod, freeze container cgroups. | Kept | Kept (in node RAM) | Reserved (Kubernetes freeze is future work) |
| `Hibernate` | Capture full pod state via the snapshot Job, then delete the Pod. | Persisted as checkpoint artifact | Persisted as checkpoint artifact | **Implemented** (replaces the OSEP-0008 path) |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I should add a mode that only preserves the filesystem. In current AI Agent scenarios, recovery can be done solely through the filesystem, without needing memory?

Comment thread oseps/0015-pod-snapshot.md Outdated
Comment thread oseps/0015-pod-snapshot.md Outdated

OpenSandbox keeps its existing `pause`/`resume` vocabulary instead of upstream's
`suspend`/`resume`, but uses an enum rather than a boolean for the CRD field.
Because OpenSandbox also needs future `Freeze`, it uses `operatingMode` as the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless it offers significant memory or disk resource benefits, the Freeze operation seems unnecessary. I recommend against introducing this enum until a concrete use case emerges.

Comment thread oseps/0015-pod-snapshot.md Outdated
- For `KeepFS` and future `Hibernate`, the snapshot reference recorded in `status.snapshot` must be
stable and durable enough to survive Pod deletion, because resume relies on it
after the Pod is gone.
- Backend authentication is provided by node/workload identity (no Kubernetes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The credential model needs a concrete identity section. Saying “node/workload identity” is not enough to implement or operate this.
Which ServiceAccount or node identity does the snapshot Job use? How is tenant isolation enforced across bucket/prefix targets? How do existing --snapshot-push-secret / --resume-pull-secret deployments migrate?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the snapshotClaim has a parameter section that can pretty much contain arbitury parameters. I would imagine that all the existing parameters will be part of a containerRegisterSnapshotClaim/Template. Does that work?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting backend parameters in SnapshotClaim/Template makes sense for non-sensitive configuration, but I think the proposal still needs to define how node/workload identity actually works.

For example, does the snapshot Job run with a controller-owned ServiceAccount, a tenant namespace ServiceAccount, or node identity? How is that identity bound to cloud IAM / registry permissions, and how do we ensure a tenant cannot point a claim at another tenant’s bucket or prefix?

Without that identity and authorization model, “node/workload identity” is still too abstract to implement or operate safely.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the wording, I wonder if it's clearer now

Signed-off-by: Ryan Zhang <zhangryan@microsoft.com>
Ryan Zhang added 2 commits July 6, 2026 09:16
Signed-off-by: Ryan Zhang <zhangryan@microsoft.com>
Signed-off-by: Ryan Zhang <zhangryan@microsoft.com>
@ryanzhang-oss ryanzhang-oss marked this pull request as ready for review July 6, 2026 10:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee5c07221f

ℹ️ 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".

(`make manifests generate`). Because `operatingMode` replaces the legacy
boolean `spec.pause`, operators need a storage-version/conversion or pre-rollout
migration that maps `pause=true` to `operatingMode=KeepFS`,
`pause=false`/unset to `operatingMode=Running`, and then drops the boolean field

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep spec.pause served during the migration

In clusters with existing automation or an older controller still using the current BatchSandbox.spec.pause field (kubernetes/apis/sandbox/v1alpha1/batchsandbox_types.go:133-139), this rollout plan removes the boolean as part of the same v1alpha1 CRD migration. Kubernetes will then prune/reject spec.pause patches while the OSEP still promises additive CRD schemas and OSEP-0008 fallback, so pause/resume can break during upgrade or rollback; keep spec.pause served/deprecated until a new storage version or dual-read compatibility window is in place.

Useful? React with 👍 / 👎.

Comment on lines +1404 to +1405
calls behave exactly as before. When the CRDs are **present**, the server switches
to the inline `spec.operatingMode`/`spec.snapshotStrategy` path. Both paths stay valid

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate inline pause on the BatchSandbox schema too

In a staged rollout where the new snapshot class/claim CRDs are applied before the BatchSandbox CRD and controller support spec.operatingMode/spec.snapshotStrategy, this detection flips the server to the inline path solely because the claim CRDs exist. The current served BatchSandbox CRD only exposes spec.pause, so empty-body /pause would start patching fields that are rejected or pruned instead of using the promised OSEP-0008 fallback; include the BatchSandbox field/schema/controller capability in the gate, or keep the legacy path until all required capabilities are present.

Useful? React with 👍 / 👎.

SnapshotClaimTemplate *SnapshotClaimTemplateSelector `json:"snapshotClaimTemplate,omitempty"`
}

// +kubebuilder:validation:Enum=Pending;Committing;Ready;Failed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the existing Succeed phase in status types

The current SandboxSnapshotPhase enum is Pending|Committing|Succeed|Failed, and §3 explicitly says there is no Ready rename, but this type sketch introduces Ready for generated status. Implementing this would make BatchSandbox.status.snapshot.phase diverge from the referenced SandboxSnapshot and from existing controllers/tests that wait for Succeed, so pause completion and GET state mapping can disagree; keep the mirrored phase on Succeed or specify a deliberate conversion.

Useful? React with 👍 / 👎.

type BatchSandboxStatus struct {
// ... existing fields ...
// +optional
Snapshot *SnapshotStatus `json:"snapshot,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Store the snapshot result reference in the type sketch

Implementations following this sketch would put the full SnapshotStatus inline on BatchSandbox and omit the result object name, but §3 says BatchSandbox.status.snapshot should shrink to a SnapshotStatusRef while per-container artifacts live on a per-pause SandboxSnapshot. Without a status.snapshot.name, resume/history/GC cannot reliably find the latest result object, so the Go sketch should use SnapshotStatusRef here or the earlier result-object contract should be changed consistently.

Useful? React with 👍 / 👎.

Comment on lines +1948 to +1950
`SandboxSnapshot` pause/resume path can be decommissioned. The CRD and
reconciler may also be retained for the explicit snapshot API; this OSEP does
not delete it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Discriminate result snapshots from action snapshots

If the SandboxSnapshotReconciler is retained for the explicit snapshot API, it currently watches every SandboxSnapshot and dispatches handlePending for empty/Pending phases (kubernetes/internal/controller/sandboxsnapshot_controller.go:136-139). The new inline controller-created result objects still have spec.sandboxName, so without a role discriminator or disabling the old reconciler for those objects, each pause result can be consumed as a new action and start a second snapshot job against the same sandbox.

Useful? React with 👍 / 👎.

Comment on lines +400 to +402
`ResourceClaimTemplate` use. The server copies the template once per sandbox/pod,
merges any allowed `snapshotClaimTemplate.parameters` into the generated claim's
`spec.parameters`, creates a distinct concrete `SandboxSnapshotClaim` for that sandbox/pod, then writes that generated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Constrain template parameter overrides

When hosted deployments expose template-backed pause to untrusted API callers, merging caller-supplied parameters into the generated claim after copying the template lets the request override storage-placement keys such as bucket or prefix. The proposal recommends exposing template names while gating only inline claims, but the template spec has no allowlist/immutable-key policy, so a caller could redirect or collide snapshot artifacts anywhere the workload identity can write; define the allowed overrides in the template or reject overrides for protected keys.

Useful? React with 👍 / 👎.

|---|---|---|
| `POST /pause` with no body | Create `SandboxSnapshot` CR + set `spec.pause` | Resolve defaults, then patch `spec.operatingMode=KeepFS` + `spec.snapshotStrategy` |
| `POST /pause` with body | Not available | Validate `operatingMode`; resolve an existing shared claim or generate one concrete claim for this sandbox/pod from a template/inline claim; patch `spec.operatingMode` and `spec.snapshotStrategy.snapshotClaimName` |
| `POST /resume` | Validate `SandboxSnapshot` Ready, set `spec.pause` | Patch `spec.operatingMode=Running` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject resume while a pause is still in flight

In the Pending/Committing window, this unconditional /resume mapping can patch operatingMode=Running while the same-node snapshot job is still running, despite the requirement that only one pause/resume transition be in progress. That can leave the job committing a stale pod and the controller racing between cleanup and resume; the server should return 409 for resume until the current pause reaches a terminal phase, just as the proposal already requires for overlapping pause requests.

Useful? React with 👍 / 👎.

1. Client POST /sandboxes/{sandboxId}/resume
2. Server Patch BatchSandbox: spec.operatingMode = Running
3. Ctrl Observe operatingMode=Running; if Pod absent and the current pause result is KeepFS:
- build Pod from spec.template

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore against the paused template version

When spec.template is edited while a sandbox is Paused, this resume flow restores the old filesystem artifact into whatever template happens to be current at resume time. Because the proposal keeps the BatchSandbox alive and does not make the template immutable or record the template generation used for the snapshot, a changed image/container set can receive mismatched writable layers or miss artifacts entirely; record and restore against the snapshotted template or reject template mutations while paused.

Useful? React with 👍 / 👎.

// it into a concrete SandboxSnapshotClaim for this sandbox/pod before setting
// SnapshotClaimName.
// +optional
SnapshotClaimTemplate *SnapshotClaimTemplateSelector `json:"snapshotClaimTemplate,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Materialize CRD template references in the controller

For direct Kubernetes users or automation that patch BatchSandbox.spec.operatingMode=KeepFS with snapshotStrategy.snapshotClaimTemplate (the examples show this CRD shape), this field is admitted on the CR but the proposal says only the server copies templates and the controller consumes only the final snapshotClaimName. Those callers bypass the server, so no concrete claim is created and the pause either fails or falls back to an unintended default; either have the BatchSandbox controller materialize this CRD field or keep template selection only in the public pause request body.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation OSEP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants