Skip to content

fix(kubernetes): keep a late stop snapshot from reviving a stopped sandbox - #5196

Merged
elezar merged 3 commits into
mainfrom
fix/5195-k8s-stop-regress/matthewgrossman
Nov 4, 2026
Merged

elezar merged 3 commits into
mainfrom
fix/5195-k8s-stop-regress/matthewgrossman

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

TL;DR

Problem: On Kubernetes, stopping a sandbox could make it look like it was starting again. The driver marks a sandbox with the same "operation in progress" flag during a start and during a stop, and it reported every flagged sandbox to the gateway as starting. A status update from the middle of a stop can arrive late, after the gateway has already marked the sandbox Stopped. The gateway took that late update as a sign that a start had begun, moved the sandbox back to Provisioning, and then rejected the next stop.

Fix: The driver now checks which operation set the flag. During a stop, it still reports the sandbox as not ready, but it no longer says a start is underway, so a late update can't bring back a stopped sandbox. Starts and restarts are reported as before.

Summary

On Kubernetes, a stopped sandbox could flip back to Provisioning with no start, and the next stop was then rejected. This happened when the driver delivered a snapshot taken during the stop after the stop had already settled. The driver now stops labelling stop-time snapshots as a starting generation.

Related Issue

Closes #5195

Changes

  • openshell-driver-kubernetes: the watch and get converter now checks which operation set the sandbox-runtime bootstrap annotations. For a stop (bootstrap-operation: "stop"), it still withdraws readiness and still holds back Suspended until the stop's completion patch, but it no longer adds Bootstrapping=True. Create, restart and annotations without an operation keep the existing marking. The marking moved into a small synchronous helper, sandbox_from_object_with_bootstrap_state, so it can be unit tested without a Kubernetes client.
  • Driver regression tests:
    • A mid-stop CR reports neither Bootstrapping=True nor Suspended=True, and isn't Ready. This fails on main.
    • Mid-create, mid-restart and no-operation CRs still report Bootstrapping=True.
  • Gateway contract test kubernetes_stop_snapshots_settle_without_reviving_the_sandbox: walks a stop through a stop-time snapshot (stays Stopping, never Provisioning or Error), the completed stop (Stopped), and a late replay of the stop-time snapshot (stays Stopped), with and without a connected supervisor session. It passes on main; the driver test covers the regression.

Why fix the driver and not add a gateway guard:

  • The gateway documents Bootstrapping=True as authoritative evidence of an accepted start. active_bootstrap_snapshot_recovers_stale_stopped_view relies on that to move a Stopped view the gateway didn't know was stale.
  • A guard keyed on a gateway-side in-flight start would break that recovery, because recovery exists for when the gateway's view lags.
  • With only the condition, the gateway can't tell a stop-time snapshot from a start-time one.
  • The driver was the side breaking the contract (the gateway code comment says "a genuine stop includes Suspended=True"). Fixing the driver leaves the start path unchanged.
  • That's also why the new gateway test passes on main: it pins the gateway's half of the contract for the snapshot shape the driver now emits.

Testing

  • Checks appropriate to the affected code and behavior pass:
    • cargo fmt --all -- --check
    • cargo clippy --locked -p openshell-driver-kubernetes -p openshell-server --all-targets -- -D warnings
    • cargo nextest run -p openshell-driver-kubernetes: 332 passed
    • cargo nextest run -p openshell-server: 2666 passed, 38 skipped (PostgreSQL-backed)
  • Unit tests added/updated: stop_snapshot_does_not_report_a_starting_generation failed before the fix and passes after it.
  • E2E tests added/updated (if applicable): no new E2E test, since the race is timing dependent. The Kubernetes lanes run conformance lifecycle::restart_stopped_sandbox, which is where it was seen.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable): not applicable

…ndbox

A stop keeps the sandbox-runtime bootstrap annotations, with operation
"stop", until its completion patch. The watch converter marked every
object with those annotations as a starting generation: it added
Bootstrapping=True and dropped Suspended. The gateway treats that
condition as an accepted start that may replace a settled Stopped
phase, so a snapshot taken mid-stop and delivered after the stop
settled moved the sandbox from Stopped to Provisioning and the next
stop was rejected.

For operation "stop", withdraw readiness and withhold the suspension
until completion as before, but do not report Bootstrapping. Create
and restart keep their existing marking, so a real start still
recovers a stale Stopped view.

Closes #5195

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman matthewgrossman added the test:e2e Requires end-to-end coverage label Nov 3, 2026
@copy-pr-bot

copy-pr-bot Bot commented Nov 3, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test d397a4a

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Label test:e2e applied, but pull-request/5196 does not exist yet. A maintainer needs to comment /ok to test d397a4acce49cba3e2c3371abb56d628aca1990d to mirror this PR. Once the mirror exists, re-apply the label or re-run Branch E2E Checks from the Actions tab.

@matthewgrossman matthewgrossman added the test:e2e-kubernetes Requires Kubernetes end-to-end coverage label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Label test:e2e-kubernetes applied for d397a4a. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute Kubernetes HA and credential-driver E2E after building the required gateway, sandbox, and supervisor images once. This is an optional proof-of-life suite; failures are visible in the workflow run but do not publish a required CI gate status.

Replace the late-stop gateway test with one that walks a stop from
Stopping through completion and then replays the in-flight snapshot, with
and without a connected supervisor session. An in-flight stop snapshot,
which now withdraws readiness without Bootstrapping or Suspended, keeps
the sandbox Stopping rather than Provisioning or Error; completion
settles it Stopped; and the late replay leaves it Stopped. The test
documents that it guards the gateway contract and that the driver test
covers the regression itself.

Share the condition reset between the stopping and bootstrapping
markers in the Kubernetes driver.

Refs #5195

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…ress/matthewgrossman

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

@purp purp left a comment

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.

LGTM 🚢

@elezar
elezar added this pull request to the merge queue Nov 4, 2026
@elezar
elezar removed this pull request from the merge queue due to a manual request Nov 4, 2026

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. The fix is scoped to the driver and leaves the start path and the gateway's stale-view recovery unchanged. I like that the driver and gateway are tested separately.

Two non-blocking observations:

  1. sandbox_from_object_with_bootstrap_state treats only "stop" specially, so any other or future operation value is reported as a start (Bootstrapping=True). That is a reasonable default, but it is the same shortcut that caused #5195, so a new operation will need to be routed deliberately.
  2. Mid-stop snapshots no longer carry any "operation in progress" signal. Before this PR they reported Bootstrapping=True. Now they report only Ready=False with DependenciesNotReady, which the gateway can't tell apart from other not-ready states. The new gateway test shows this settles correctly from Stopping, but I don't see coverage for a stale gateway view (for example still Running) receiving such a snapshot. This is worth covering in a follow-up, along with better-defined operation and generation semantics for the sandbox conditions.

Approving as is.

@elezar
elezar added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit f4a6137 Nov 4, 2026
162 of 163 checks passed
@elezar
elezar deleted the fix/5195-k8s-stop-regress/matthewgrossman branch November 4, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage test:e2e-kubernetes Requires Kubernetes end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(kubernetes): stopped sandbox regresses to Provisioning after stop completes

3 participants