From ecc33243c094b406848b9f1159c961d4d959d3f2 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 15:48:14 -0500 Subject: [PATCH 01/15] feat(sandbox): add suspend and resume operations Signed-off-by: Seth Jennings --- .../skills/debug-openshell-cluster/SKILL.md | 1 + .agents/skills/openshell-cli/SKILL.md | 19 +- .agents/skills/openshell-cli/cli-reference.md | 13 + architecture/compute-runtimes.md | 33 +- crates/openshell-cli/src/commands/common.rs | 3 + crates/openshell-cli/src/main.rs | 45 + crates/openshell-cli/src/run.rs | 133 +- .../tests/ensure_providers_integration.rs | 14 + .../openshell-cli/tests/mtls_integration.rs | 14 + .../tests/provider_commands_integration.rs | 14 + .../sandbox_create_lifecycle_integration.rs | 14 + .../sandbox_name_fallback_integration.rs | 14 + crates/openshell-core/src/error.rs | 4 + crates/openshell-core/src/telemetry.rs | 4 + crates/openshell-driver-docker/README.md | 9 + crates/openshell-driver-docker/src/lib.rs | 27 +- crates/openshell-driver-kubernetes/README.md | 7 + .../openshell-driver-kubernetes/src/driver.rs | 153 +- .../openshell-driver-kubernetes/src/grpc.rs | 44 +- crates/openshell-driver-podman/README.md | 9 + crates/openshell-driver-podman/src/driver.rs | 91 +- crates/openshell-driver-podman/src/grpc.rs | 28 +- crates/openshell-driver-vm/README.md | 7 + crates/openshell-driver-vm/src/driver.rs | 185 +- crates/openshell-driver-vm/src/lifecycle.rs | 2 + .../openshell-driver-vm/src/otel_tracing.rs | 2 + crates/openshell-sdk/src/client.rs | 58 + crates/openshell-sdk/src/raw.rs | 6 +- crates/openshell-sdk/src/types.rs | 6 + crates/openshell-sdk/tests/client_mock.rs | 57 + .../openshell-server/src/auth/method_authz.rs | 14 + .../src/auth/sandbox_methods.rs | 6 + crates/openshell-server/src/compute/mod.rs | 736 +++++- crates/openshell-server/src/grpc/mod.rs | 21 +- crates/openshell-server/src/grpc/sandbox.rs | 122 +- .../src/supervisor_session.rs | 14 + crates/openshell-server/src/test_support.rs | 26 +- crates/openshell-server/tests/common/mod.rs | 14 + .../tests/supervisor_relay_integration.rs | 12 + docs/reference/sandbox-compute-drivers.mdx | 26 +- docs/sandboxes/manage-sandboxes.mdx | 23 + e2e/rust/tests/sandbox_lifecycle.rs | 81 + proto/compute_driver.proto | 14 +- proto/openshell.proto | 37 + python/openshell/sandbox.py | 58 +- python/openshell/sandbox_test.py | 51 + .../v1/internal/converter/sandbox.go | 12 + .../v1/internal/converter/sandbox_test.go | 6 + sdk/go/openshell/v1/sandbox.go | 3 + sdk/go/openshell/v1/sandbox_client.go | 34 +- sdk/go/openshell/v1/sandbox_client_test.go | 54 + sdk/go/openshell/v1/types.go | 3 + sdk/go/openshell/v1/types/types.go | 3 + sdk/go/proto/openshellv1/openshell.pb.go | 1978 +++++++++-------- sdk/go/proto/openshellv1/openshell_grpc.pb.go | 80 + 55 files changed, 3420 insertions(+), 1024 deletions(-) diff --git a/.agents/skills/debug-openshell-cluster/SKILL.md b/.agents/skills/debug-openshell-cluster/SKILL.md index cc77d771b2..4e4678a1fc 100644 --- a/.agents/skills/debug-openshell-cluster/SKILL.md +++ b/.agents/skills/debug-openshell-cluster/SKILL.md @@ -464,6 +464,7 @@ openshell logs | Supervisor enters policy quarantine | A runtime candidate failed validation while `policy_validation_failure_mode = "fail_closed"` | Sandbox OCSF config/finding events, validation rationale, active generation, `previous_policy_active` | | HTTP request returns `middleware_failed` or `middleware_denied` | Selected stage failed or explicitly denied the admitted request | Sandbox OCSF logs; policy-local middleware config; service availability; `on_error` | | Custom compute driver is unavailable | Driver process/socket missing, inaccessible, or configured with a reserved/mismatched name | Socket ownership/mode, driver service logs, gateway `GetCapabilities` logs | +| Sandbox remains `Suspending` or `Resuming` | Driver stop/start failed, retained resource is missing, or the resumed supervisor has not reconnected | Gateway and driver logs; `docker inspect`, `podman inspect`, Agent Sandbox status/PVC, or VM state marker and launcher process | | Image pull failure | Gateway or sandbox image cannot be pulled | Runtime events and image pull credentials | | `K8s namespace not ready` with `envoy-gateway-openshell.yaml: the server could not find the requested resource` | Optional Gateway API manifest was applied without Envoy Gateway CRDs, or k3s Helm controller startup exceeded the namespace wait | Apply `deploy/kube/manifests/envoy-gateway-openshell.yaml` manually only after Envoy Gateway is installed and `grpcRoute` is enabled | | HTTPS ingress (`grpcRoute.gateway.listener.protocol=HTTPS`) connection resets or TLS handshake hangs | Envoy terminates TLS but the gateway pod still expects TLS, so the plaintext backend hop fails | Set `server.disableTls=true` so Envoy forwards plaintext to the pod; verify the listener `certificateRefs` Secret exists in the release namespace and `openshell status` over `https://` | diff --git a/.agents/skills/openshell-cli/SKILL.md b/.agents/skills/openshell-cli/SKILL.md index 462e27f3f2..ee7df3d751 100644 --- a/.agents/skills/openshell-cli/SKILL.md +++ b/.agents/skills/openshell-cli/SKILL.md @@ -298,6 +298,21 @@ openshell sandbox delete sandbox-1 sandbox-2 sandbox-3 # Multiple at once openshell sandbox delete --all ``` +### Suspend and resume sandboxes + +Use suspension to stop compute while retaining the sandbox and its persistent +workspace: + +```bash +openshell sandbox suspend [name] +openshell sandbox resume [name] +``` + +Both commands default to the last-used sandbox. Suspend stops background +forwards and waits for `Suspended`; resume waits for `Ready`. Connect, exec, +file transfer, forwarding, and exposed services are unavailable while +suspended. Delete remains the operation that removes retained state. + --- ## Workflow 4: Policy Iteration Loop @@ -669,7 +684,7 @@ The CLI help is always authoritative. If the help output contradicts this skill, ```bash $ openshell sandbox --help -# Shows: create, get, list, delete, exec, connect, upload, download, ssh-config, provider +# Shows: create, get, list, suspend, resume, delete, exec, connect, upload, download, ssh-config, provider $ openshell sandbox upload --help # Shows: positional arguments (name, path, dest), usage examples @@ -691,6 +706,8 @@ $ openshell sandbox upload --help | Create sandbox with GPUs | `openshell sandbox create --gpu 1` | | Create with custom policy | `openshell sandbox create --policy ./p.yaml` | | Connect to sandbox | `openshell sandbox connect ` | +| Suspend sandbox compute | `openshell sandbox suspend [name]` | +| Resume sandbox compute | `openshell sandbox resume [name]` | | Execute in sandbox | `openshell sandbox exec --name -- ` | | Stream live logs | `openshell logs --tail` | | Incremental policy update | `openshell policy update --add-endpoint host:443:read-only:rest:enforce --binary /usr/bin/curl --wait` | diff --git a/.agents/skills/openshell-cli/cli-reference.md b/.agents/skills/openshell-cli/cli-reference.md index 30d6fb7ed3..801cf09ea2 100644 --- a/.agents/skills/openshell-cli/cli-reference.md +++ b/.agents/skills/openshell-cli/cli-reference.md @@ -48,6 +48,8 @@ openshell │ ├── create [opts] [-- CMD...] │ ├── get [name] │ ├── list [opts] +│ ├── suspend [name] +│ ├── resume [name] │ ├── delete [name]... [--all] │ ├── exec [--name ] [opts] -- CMD... │ ├── connect [name] [--editor ] @@ -250,6 +252,17 @@ Show sandbox details and the active policy. Metadata identifies sandbox or globa Delete one or more named sandboxes, or use `--all`. Deletion stops background port forwards. +### `openshell sandbox suspend [name]` + +Stop sandbox compute while retaining the sandbox and persistent workspace. The +name defaults to the last-used sandbox. The command stops background forwards +and waits for the `Suspended` phase. + +### `openshell sandbox resume [name]` + +Restart a suspended sandbox and wait for `Ready`. The name defaults to the +last-used sandbox. + ### `openshell sandbox exec [OPTIONS] -- COMMAND...` Execute a command through the gRPC exec endpoint, stream its output, and exit with the remote command's exit code. diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index 646b6320bd..7bf8ac5974 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -1,6 +1,6 @@ # Compute Runtimes -Compute runtimes create, stop, delete, and watch sandbox workloads for the +Compute runtimes create, suspend, resume, delete, and watch sandbox workloads for the gateway. They do not replace sandbox policy enforcement. Every runtime starts a workload that runs the `openshell-sandbox` supervisor, and the supervisor enforces the sandbox contract locally. @@ -84,19 +84,42 @@ The gateway records driver identity and version from the startup capability response. Elevated gateway info reports that initialized driver snapshot instead of re-querying drivers on each request. +## Suspend and Resume Lifecycle + +The gateway persists lifecycle intent before mutating compute: + +```text +Ready -> Suspending -> Suspended -> Resuming -> Ready +``` + +`StopSandbox` and `ResumeSandbox` are idempotent driver operations. Suspension +retains the driver resource and its persistent workspace boundary while making +exec, SSH, forwarding, and exposed services unavailable. Resume reactivates the +same resource. The gateway requires a fresh supervisor session before a +resuming sandbox returns to `Ready`; stale driver snapshots and supervisor +sessions cannot promote a `Suspended` row. + +Persisted `Suspending` and `Resuming` rows are retried at startup. Stable +`Suspended` rows remain stopped. Docker and Podman retain the stopped container +and attached storage, Kubernetes retains the Sandbox CR and PVC while scaling +compute to zero, and VM retains its launch request and writable overlay beside +a suspension marker. Delete remains a separate operation that removes these +resources. + ## Deletion Lifecycle -Delete requests use per-sandbox gates to serialize delete attempts. A request +Lifecycle requests use per-sandbox gates to serialize suspend, resume, and +delete attempts. A delete request resolves the name once and remains bound to that stable ID. The only -combined lock order is delete gate, then the gateway-wide state guard; external +combined lock order is lifecycle gate, then the gateway-wide state guard; external driver calls run without the global guard. -Delete gates are process-local and do not coordinate gateway replicas. They +Lifecycle gates are process-local and do not coordinate gateway replicas. They serialize attempts rather than share results: if one attempt fails and recovery restores a deletable state, a request waiting on the gate may retry the driver. Persisted resource-version checks remain the cross-replica safety boundary. -Watcher events do not acquire delete gates. Exact resource-version checks allow +Watcher events do not acquire lifecycle gates. Exact resource-version checks allow them to interleave safely: status snapshots are no-ops for `Deleting` rows, deleted events are idempotent, and snapshots for absent rows are ignored. diff --git a/crates/openshell-cli/src/commands/common.rs b/crates/openshell-cli/src/commands/common.rs index e6edb4d33a..45554417bc 100644 --- a/crates/openshell-cli/src/commands/common.rs +++ b/crates/openshell-cli/src/commands/common.rs @@ -59,6 +59,9 @@ pub fn phase_name(phase: i32) -> &'static str { Ok(SandboxPhase::Ready) => "Ready", Ok(SandboxPhase::Error) => "Error", Ok(SandboxPhase::Deleting) => "Deleting", + Ok(SandboxPhase::Suspending) => "Suspending", + Ok(SandboxPhase::Suspended) => "Suspended", + Ok(SandboxPhase::Resuming) => "Resuming", Ok(SandboxPhase::Unknown) | Err(_) => "Unknown", } } diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 4ea2765d25..5058ccec00 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -1520,6 +1520,22 @@ enum SandboxCommands { all: bool, }, + /// Suspend a sandbox while preserving its workspace. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Suspend { + /// Sandbox name (defaults to last-used sandbox). + #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] + name: Option, + }, + + /// Resume a suspended sandbox. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Resume { + /// Sandbox name (defaults to last-used sandbox). + #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] + name: Option, + }, + /// Execute a command in a running sandbox. /// /// Runs a command inside an existing sandbox using the gRPC exec endpoint. @@ -3132,6 +3148,14 @@ async fn main() -> Result<()> { ) .await?; } + SandboxCommands::Suspend { name } => { + let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; + run::sandbox_suspend(endpoint, &name, &cli.workspace, &tls).await?; + } + SandboxCommands::Resume { name } => { + let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; + run::sandbox_resume(endpoint, &name, &cli.workspace, &tls).await?; + } SandboxCommands::Connect { name, editor } => { let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; if let Some(editor) = editor.map(Into::into) { @@ -4432,6 +4456,27 @@ mod tests { )); } + #[test] + fn sandbox_suspend_and_resume_accept_optional_names() { + let suspend = Cli::try_parse_from(["openshell", "sandbox", "suspend", "demo"]) + .expect("suspend command should parse"); + assert!(matches!( + suspend.command, + Some(Commands::Sandbox { + command: Some(SandboxCommands::Suspend { name: Some(ref name) }), + }) if name == "demo" + )); + + let resume = Cli::try_parse_from(["openshell", "sandbox", "resume"]) + .expect("resume command should parse"); + assert!(matches!( + resume.command, + Some(Commands::Sandbox { + command: Some(SandboxCommands::Resume { name: None }), + }) + )); + } + #[test] fn sandbox_list_accepts_output_json() { let cli = Cli::try_parse_from(["openshell", "sandbox", "list", "-o", "json"]) diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 48bf2d3dd5..f76022037c 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -48,10 +48,10 @@ use openshell_core::proto::{ ListSandboxPoliciesRequest, ListSandboxProvidersRequest, ListSandboxesRequest, ListServicesRequest, PolicySource, PolicyStatus, Provider, ProviderCredentialRefreshStatus, ProviderCredentialRefreshStrategy, ProviderProfile, ProviderProfileDiagnostic, - ProviderProfileImportItem, RejectDraftChunkRequest, ResourceRequirements, + ProviderProfileImportItem, RejectDraftChunkRequest, ResourceRequirements, ResumeSandboxRequest, RevokeSshSessionRequest, RotateProviderCredentialRequest, Sandbox, SandboxPhase, SandboxPolicy, SandboxSpec, SandboxTemplate, ServiceEndpointResponse, SetInferenceRouteRequest, SettingScope, - TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, + SuspendSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, exec_sandbox_event, setting_value, tcp_forward_init, }; @@ -2419,6 +2419,135 @@ pub async fn sandbox_delete( Ok(()) } +/// Suspend a sandbox while retaining its persistent workspace. +pub async fn sandbox_suspend( + server: &str, + name: &str, + workspace: &str, + tls: &TlsOptions, +) -> Result<()> { + if let Ok(stopped) = stop_forwards_for_sandbox(name) { + for port in stopped { + eprintln!( + "{} Stopped forward of port {port} for sandbox {name}", + "✓".green().bold(), + ); + } + } + + let mut client = grpc_client(server, tls).await?; + let sandbox = client + .suspend_sandbox(SuspendSandboxRequest { + name: name.to_string(), + workspace: workspace.to_string(), + }) + .await + .into_diagnostic()? + .into_inner() + .sandbox + .ok_or_else(|| miette!("gateway returned no sandbox after suspend"))?; + wait_for_lifecycle_phase(&mut client, sandbox, SandboxPhase::Suspended).await?; + println!("{} Suspended sandbox {name}", "✓".green().bold()); + Ok(()) +} + +/// Resume a suspended sandbox and wait until it is ready. +pub async fn sandbox_resume( + server: &str, + name: &str, + workspace: &str, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let sandbox = client + .resume_sandbox(ResumeSandboxRequest { + name: name.to_string(), + workspace: workspace.to_string(), + }) + .await + .into_diagnostic()? + .into_inner() + .sandbox + .ok_or_else(|| miette!("gateway returned no sandbox after resume"))?; + wait_for_lifecycle_phase(&mut client, sandbox, SandboxPhase::Ready).await?; + println!("{} Resumed sandbox {name}", "✓".green().bold()); + Ok(()) +} + +async fn wait_for_lifecycle_phase( + client: &mut crate::tls::GrpcClient, + sandbox: Sandbox, + target: SandboxPhase, +) -> Result { + let current = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); + if current == target { + return Ok(sandbox); + } + if current == SandboxPhase::Error { + return Err(miette!( + "sandbox entered Error while waiting for {target:?}" + )); + } + + let timeout = Duration::from_secs( + std::env::var("OPENSHELL_LIFECYCLE_TIMEOUT") + .ok() + .and_then(|value| value.parse().ok()) + .unwrap_or(300), + ); + let sandbox_id = sandbox.object_id().to_string(); + let mut stream = client + .watch_sandbox(WatchSandboxRequest { + id: sandbox_id, + follow_status: true, + follow_logs: false, + follow_events: false, + log_tail_lines: 0, + event_tail: 0, + stop_on_terminal: false, + log_since_ms: 0, + log_sources: Vec::new(), + log_min_level: String::new(), + }) + .await + .into_diagnostic()? + .into_inner(); + + let deadline = Instant::now() + timeout; + loop { + let remaining = deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + return Err(miette!( + "timed out after {}s waiting for sandbox to reach {target:?}", + timeout.as_secs() + )); + } + let event = tokio::time::timeout(remaining, stream.next()) + .await + .map_err(|_| { + miette!( + "timed out after {}s waiting for sandbox to reach {target:?}", + timeout.as_secs() + ) + })? + .ok_or_else(|| miette!("sandbox watch ended before reaching {target:?}"))? + .into_diagnostic()?; + if let Some(openshell_core::proto::sandbox_stream_event::Payload::Sandbox(sandbox)) = + event.payload + { + let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); + if phase == target { + return Ok(sandbox); + } + if phase == SandboxPhase::Error { + let detail = ready_false_condition_message(sandbox.status.as_ref()) + .unwrap_or_else(|| "sandbox entered Error".to_string()); + return Err(miette!(detail)); + } + } + } +} + /// Return the provider type inferred from the trailing command, if any. fn inferred_provider_type(command: &[String]) -> Option { detect_provider_from_command(command).map(str::to_string) diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index 5bd64c2f36..5b9fc87ab0 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -111,6 +111,20 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } + async fn suspend_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn resume_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index 38c68ed83a..8569fd4cc2 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -66,6 +66,20 @@ impl OpenShell for TestOpenShell { )) } + async fn suspend_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn resume_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 24645ea259..26ff7b2f62 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -129,6 +129,20 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } + async fn suspend_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn resume_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 8bacb76a2d..0314ee4417 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -114,6 +114,20 @@ impl OpenShell for TestOpenShell { })) } + async fn suspend_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn resume_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 019b2b12e4..23ad7ff258 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -79,6 +79,20 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } + async fn suspend_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn resume_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, diff --git a/crates/openshell-core/src/error.rs b/crates/openshell-core/src/error.rs index a149cf006a..8c23e30198 100644 --- a/crates/openshell-core/src/error.rs +++ b/crates/openshell-core/src/error.rs @@ -113,6 +113,9 @@ pub enum ComputeDriverError { /// The requested sandbox already exists. #[error("sandbox already exists")] AlreadyExists, + /// The requested sandbox does not exist. + #[error("sandbox not found")] + NotFound, /// The request contains an invalid argument. #[error("{0}")] InvalidArgument(String), @@ -128,6 +131,7 @@ impl From for tonic::Status { fn from(err: ComputeDriverError) -> Self { match err { ComputeDriverError::AlreadyExists => Self::already_exists("sandbox already exists"), + ComputeDriverError::NotFound => Self::not_found("sandbox not found"), ComputeDriverError::InvalidArgument(m) => Self::invalid_argument(m), ComputeDriverError::Precondition(m) => Self::failed_precondition(m), ComputeDriverError::Message(m) => Self::internal(m), diff --git a/crates/openshell-core/src/telemetry.rs b/crates/openshell-core/src/telemetry.rs index 49ce620f4f..f7a1cdfd1e 100644 --- a/crates/openshell-core/src/telemetry.rs +++ b/crates/openshell-core/src/telemetry.rs @@ -103,6 +103,8 @@ impl LifecycleResource { pub enum LifecycleOperation { Create, Delete, + Suspend, + Resume, Update, } @@ -112,6 +114,8 @@ impl LifecycleOperation { match self { Self::Create => "create", Self::Delete => "delete", + Self::Suspend => "suspend", + Self::Resume => "resume", Self::Update => "update", } } diff --git a/crates/openshell-driver-docker/README.md b/crates/openshell-driver-docker/README.md index 05faf53c5c..0dd4320bdf 100644 --- a/crates/openshell-driver-docker/README.md +++ b/crates/openshell-driver-docker/README.md @@ -18,6 +18,15 @@ The gateway runs as a host process. The Docker driver creates one container per sandbox and starts the `openshell-sandbox` supervisor inside that container. The supervisor then creates the nested sandbox namespace for the agent process. +## Suspend and Resume + +Suspend stops the managed container without removing it. Docker retains the +container writable layer, attached volumes, labels, token material, and restart +policy. Resume starts that same container, so files in the resolved OCI +workspace remain available. A durably suspended sandbox is excluded from +gateway startup recovery and stays stopped across gateway restarts. Delete +continues to force-remove the container and clean up driver-owned material. + Before creating the container, the driver inspects the final sandbox image and captures its immutable image ID, raw OCI `Config.User`, and OCI `Config.WorkingDir`. Container creation uses that image ID, preventing a diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index dd4d9ef0f0..3eb6d33151 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -42,11 +42,12 @@ use openshell_core::proto::compute::v1::{ DriverSandboxTemplate, GatewayListenerRequirement, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, - GpuResourceRequirements, ListSandboxesRequest, ListSandboxesResponse, StopSandboxRequest, - StopSandboxResponse, ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, - WatchSandboxesDeletedEvent, WatchSandboxesEvent, WatchSandboxesPlatformEvent, - WatchSandboxesRequest, WatchSandboxesSandboxEvent, compute_driver_server::ComputeDriver, - gateway_listener_requirement::Selector, watch_sandboxes_event, + GpuResourceRequirements, ListSandboxesRequest, ListSandboxesResponse, ResumeSandboxRequest, + ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, + ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, WatchSandboxesEvent, + WatchSandboxesPlatformEvent, WatchSandboxesRequest, WatchSandboxesSandboxEvent, + compute_driver_server::ComputeDriver, gateway_listener_requirement::Selector, + watch_sandboxes_event, }; use openshell_core::proto_struct::{ deserialize_optional_non_empty_string_list, struct_to_json_value, @@ -1479,9 +1480,25 @@ impl ComputeDriver for DockerComputeDriver { self.stop_sandbox_inner(&request.sandbox_id, &request.sandbox_name) .await?; + self.publish_container_snapshot(&request.sandbox_id, &request.sandbox_name) + .await?; Ok(Response::new(StopSandboxResponse {})) } + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + require_sandbox_identifier(&request.sandbox_id, &request.sandbox_name)?; + if !Self::resume_sandbox(self, &request.sandbox_id, &request.sandbox_name).await? { + return Err(Status::not_found("sandbox not found")); + } + self.publish_container_snapshot(&request.sandbox_id, &request.sandbox_name) + .await?; + Ok(Response::new(ResumeSandboxResponse {})) + } + async fn delete_sandbox( &self, request: Request, diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index 1356e2d932..78b99eb0cb 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -36,6 +36,13 @@ This is a stopgap persistence model. It preserves user files across pod rescheduling but duplicates the base workspace and does not automatically apply image updates to existing PVCs. Future snapshotting should replace it. +Suspend preserves the Agent Sandbox resource and workspace PVC while stopping +its pod. The driver sets `spec.operatingMode: Suspended` for `v1beta1` or +`spec.replicas: 0` for `v1alpha1`. Resume sets `Running` or one replica for the +same resource, so the replacement pod mounts the existing claim. Delete is the +only lifecycle operation that removes the Sandbox resource and its owned +storage. + The workspace PVC size defaults to `workspace_default_storage_size`. Set `workspace_storage_class` to pin the PVC to a specific `StorageClass`; an empty value omits `storageClassName` so the cluster's default `StorageClass` applies. diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 2f1ea72a32..722943e154 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -13,7 +13,9 @@ use futures::{Stream, StreamExt, TryStreamExt}; use k8s_openapi::api::core::v1::{ Event as KubeEventObj, Namespace, Node, PersistentVolumeClaimVolumeSource, Volume, VolumeMount, }; -use kube::api::{Api, ApiResource, DeleteParams, ListParams, PostParams, Preconditions}; +use kube::api::{ + Api, ApiResource, DeleteParams, ListParams, Patch, PatchParams, PostParams, Preconditions, +}; use kube::core::gvk::GroupVersionKind; use kube::core::{DynamicObject, ObjectMeta}; use kube::runtime::watcher::{self, Event}; @@ -927,6 +929,102 @@ impl KubernetesComputeDriver { } } + pub async fn stop_sandbox(&self, sandbox_id: &str) -> Result<(), String> { + let (agent_sandbox_api, kube_name) = self + .patch_sandbox_operating_state(sandbox_id, false) + .await?; + + let deadline = tokio::time::Instant::now() + KUBE_API_TIMEOUT; + loop { + let object = agent_sandbox_api + .api + .get(&kube_name) + .await + .map_err(|err| err.to_string())?; + if kubernetes_sandbox_is_suspended(&object) { + return Ok(()); + } + if tokio::time::Instant::now() >= deadline { + return Err(format!( + "timed out after {}s waiting for Kubernetes sandbox to suspend", + KUBE_API_TIMEOUT.as_secs() + )); + } + tokio::time::sleep(Duration::from_millis(250)).await; + } + } + + pub async fn resume_sandbox(&self, sandbox_id: &str) -> Result<(), String> { + self.patch_sandbox_operating_state(sandbox_id, true) + .await + .map(|_| ()) + } + + async fn patch_sandbox_operating_state( + &self, + sandbox_id: &str, + running: bool, + ) -> Result<(AgentSandboxApi, String), String> { + let agent_sandbox_api = self + .supported_agent_sandbox_api(self.client.clone()) + .await?; + let selector = + format!("{LABEL_MANAGED_BY}={LABEL_MANAGED_BY_VALUE},{LABEL_SANDBOX_ID}={sandbox_id}"); + let list = tokio::time::timeout( + KUBE_API_TIMEOUT, + agent_sandbox_api + .api + .list(&ListParams::default().labels(&selector)), + ) + .await + .map_err(|_| { + format!( + "timed out after {}s waiting for Kubernetes API", + KUBE_API_TIMEOUT.as_secs() + ) + })? + .map_err(|err| err.to_string())?; + let object = list + .items + .into_iter() + .next() + .ok_or_else(|| "sandbox not found".to_string())?; + let kube_name = object + .metadata + .name + .ok_or_else(|| "sandbox resource has no name".to_string())?; + let resource_version = object.metadata.resource_version.unwrap_or_default(); + let desired = sandbox_operating_state_patch( + &agent_sandbox_api.resource.version, + &resource_version, + running, + ); + tokio::time::timeout( + KUBE_API_TIMEOUT, + agent_sandbox_api.api.patch( + &kube_name, + &PatchParams::default(), + &Patch::Merge(&desired), + ), + ) + .await + .map_err(|_| { + format!( + "timed out after {}s waiting for Kubernetes API", + KUBE_API_TIMEOUT.as_secs() + ) + })? + .map_err(|err| err.to_string())?; + + info!( + sandbox_id, + sandbox_api_version = %agent_sandbox_api.resource.version, + running, + "Updated Kubernetes sandbox operating state" + ); + Ok((agent_sandbox_api, kube_name)) + } + pub async fn delete_sandbox(&self, sandbox_id: &str) -> Result { info!( sandbox_id = %sandbox_id, @@ -3121,6 +3219,46 @@ fn status_from_object(obj: &DynamicObject) -> Option { }) } +fn kubernetes_sandbox_is_suspended(obj: &DynamicObject) -> bool { + obj.data + .get("status") + .and_then(|status| status.get("conditions")) + .and_then(serde_json::Value::as_array) + .is_some_and(|conditions| { + conditions.iter().any(|condition| { + condition.get("type").and_then(serde_json::Value::as_str) == Some("Suspended") + && condition + .get("status") + .and_then(serde_json::Value::as_str) + .is_some_and(|status| status.eq_ignore_ascii_case("true")) + }) + }) + || obj + .data + .get("status") + .and_then(|status| status.get("replicas")) + .and_then(serde_json::Value::as_i64) + == Some(0) +} + +fn sandbox_operating_state_patch( + api_version: &str, + resource_version: &str, + running: bool, +) -> serde_json::Value { + if api_version == SANDBOX_VERSION_V1BETA1 { + serde_json::json!({ + "metadata": {"resourceVersion": resource_version}, + "spec": {"operatingMode": if running { "Running" } else { "Suspended" }} + }) + } else { + serde_json::json!({ + "metadata": {"resourceVersion": resource_version}, + "spec": {"replicas": i32::from(running)} + }) + } +} + fn condition_from_value(value: &serde_json::Value) -> Option { let obj = value.as_object()?; Some(SandboxCondition { @@ -3194,6 +3332,19 @@ mod tests { assert!(should_try_next_sandbox_api_version(&raw)); } + #[test] + fn lifecycle_patch_uses_version_specific_operating_state() { + let beta_suspend = sandbox_operating_state_patch(SANDBOX_VERSION_V1BETA1, "42", false); + assert_eq!(beta_suspend["metadata"]["resourceVersion"], "42"); + assert_eq!(beta_suspend["spec"]["operatingMode"], "Suspended"); + assert!(beta_suspend["spec"].get("replicas").is_none()); + + let alpha_resume = sandbox_operating_state_patch(SANDBOX_VERSION_V1ALPHA1, "43", true); + assert_eq!(alpha_resume["metadata"]["resourceVersion"], "43"); + assert_eq!(alpha_resume["spec"]["replicas"], 1); + assert!(alpha_resume["spec"].get("operatingMode").is_none()); + } + #[test] fn sandbox_api_version_probe_keeps_non_404_errors() { let err = kube_api_error(403, "sandboxes.agents.x-k8s.io is forbidden"); diff --git a/crates/openshell-driver-kubernetes/src/grpc.rs b/crates/openshell-driver-kubernetes/src/grpc.rs index 6eeb51cd73..e3b81d27fd 100644 --- a/crates/openshell-driver-kubernetes/src/grpc.rs +++ b/crates/openshell-driver-kubernetes/src/grpc.rs @@ -8,9 +8,10 @@ use openshell_core::proto::compute::v1::{ CreateSandboxRequest, CreateSandboxResponse, DeleteSandboxRequest, DeleteSandboxResponse, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, - ListSandboxesRequest, ListSandboxesResponse, StopSandboxRequest, StopSandboxResponse, - ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, - WatchSandboxesRequest, compute_driver_server::ComputeDriver, + ListSandboxesRequest, ListSandboxesResponse, ResumeSandboxRequest, ResumeSandboxResponse, + StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, + ValidateSandboxCreateResponse, WatchSandboxesEvent, WatchSandboxesRequest, + compute_driver_server::ComputeDriver, }; use std::pin::Pin; use tonic::{Request, Response, Status}; @@ -112,11 +113,32 @@ impl ComputeDriver for ComputeDriverService { async fn stop_sandbox( &self, - _request: Request, + request: Request, ) -> Result, Status> { - Err(Status::unimplemented( - "stop sandbox is not implemented by the kubernetes compute driver", - )) + let request = request.into_inner(); + if request.sandbox_id.is_empty() { + return Err(Status::invalid_argument("sandbox_id is required")); + } + self.driver + .stop_sandbox(&request.sandbox_id) + .await + .map_err(kubernetes_lifecycle_status)?; + Ok(Response::new(StopSandboxResponse {})) + } + + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + if request.sandbox_id.is_empty() { + return Err(Status::invalid_argument("sandbox_id is required")); + } + self.driver + .resume_sandbox(&request.sandbox_id) + .await + .map_err(kubernetes_lifecycle_status)?; + Ok(Response::new(ResumeSandboxResponse {})) } async fn delete_sandbox( @@ -152,6 +174,14 @@ impl ComputeDriver for ComputeDriverService { } } +fn kubernetes_lifecycle_status(message: String) -> Status { + if message == "sandbox not found" { + Status::not_found(message) + } else { + Status::internal(message) + } +} + #[cfg(test)] mod tests { use crate::KubernetesDriverError; diff --git a/crates/openshell-driver-podman/README.md b/crates/openshell-driver-podman/README.md index 965a295d19..3129b84be2 100644 --- a/crates/openshell-driver-podman/README.md +++ b/crates/openshell-driver-podman/README.md @@ -19,6 +19,15 @@ independently. For a rootless networking deep dive, see [NETWORKING.md](NETWORKING.md). +## Suspend and Resume + +Suspend stops the managed container without deleting it. The per-sandbox named +workspace volume, token and proxy-auth secrets, labels, and container metadata +remain intact. Resume starts the same container and reuses the same named +volume. Stopped managed containers remain visible through list and watch +reconciliation. Delete remains responsible for removing the container, +driver-owned secrets, and workspace volume. + ## Architecture The Podman driver communicates with the Podman daemon over a Unix socket and diff --git a/crates/openshell-driver-podman/src/driver.rs b/crates/openshell-driver-podman/src/driver.rs index 51c689fb29..26f9b2f1a3 100644 --- a/crates/openshell-driver-podman/src/driver.rs +++ b/crates/openshell-driver-podman/src/driver.rs @@ -3,7 +3,7 @@ //! Podman compute driver. -use crate::client::{PodmanApiError, PodmanClient, VolumeInspect}; +use crate::client::{ContainerListEntry, PodmanApiError, PodmanClient, VolumeInspect}; use crate::config::PodmanComputeConfig; use crate::container::{self, LABEL_MANAGED_FILTER, LABEL_SANDBOX_ID, PodmanSandboxDriverConfig}; use crate::watcher::{ @@ -36,7 +36,7 @@ impl From for ComputeDriverError { fn from(value: PodmanApiError) -> Self { match value { PodmanApiError::Conflict(_) => Self::AlreadyExists, - PodmanApiError::NotFound(msg) => Self::Message(format!("not found: {msg}")), + PodmanApiError::NotFound(_) => Self::NotFound, other => Self::Message(other.to_string()), } } @@ -815,20 +815,32 @@ impl PodmanComputeDriver { &self, sandbox_id: &str, ) -> Result, ComputeDriverError> { + Ok(self.find_container(sandbox_id).await?.map(|entry| entry.id)) + } + + async fn find_container( + &self, + sandbox_id: &str, + ) -> Result, ComputeDriverError> { let id_filter = format!("{LABEL_SANDBOX_ID}={sandbox_id}"); let entries = self .client .list_containers(&[LABEL_MANAGED_FILTER, &id_filter]) .await .map_err(ComputeDriverError::from)?; - Ok(entries.first().map(|e| e.id.clone())) + Ok(entries.into_iter().next()) } /// Stop a sandbox container without deleting it. pub async fn stop_sandbox(&self, sandbox_id: &str) -> Result<(), ComputeDriverError> { - let container_id = self.find_container_id(sandbox_id).await?.ok_or_else(|| { - ComputeDriverError::Precondition("sandbox container not found".into()) - })?; + let container = self + .find_container(sandbox_id) + .await? + .ok_or(ComputeDriverError::NotFound)?; + if container.state != "running" { + return Ok(()); + } + let container_id = container.id; info!(sandbox_id = %sandbox_id, container = %container_id, "Stopping sandbox container"); self.client @@ -837,6 +849,24 @@ impl PodmanComputeDriver { .map_err(ComputeDriverError::from) } + /// Resume a previously stopped sandbox container. + pub async fn resume_sandbox(&self, sandbox_id: &str) -> Result<(), ComputeDriverError> { + let container = self + .find_container(sandbox_id) + .await? + .ok_or(ComputeDriverError::NotFound)?; + if container.state == "running" { + return Ok(()); + } + let container_id = container.id; + info!(sandbox_id = %sandbox_id, container = %container_id, "Resuming sandbox container"); + + self.client + .start_container(&container_id) + .await + .map_err(ComputeDriverError::from) + } + /// Delete a sandbox container and its workspace volume. pub async fn delete_sandbox(&self, sandbox_id: &str) -> Result { if sandbox_id.is_empty() { @@ -1172,7 +1202,54 @@ mod tests { #[test] fn podman_driver_error_from_not_found() { let err = ComputeDriverError::from(PodmanApiError::NotFound("gone".into())); - assert!(matches!(err, ComputeDriverError::Message(_))); + assert!(matches!(err, ComputeDriverError::NotFound)); + } + + #[tokio::test] + async fn stop_and_resume_target_the_existing_container() { + let (stop_socket, stop_requests, stop_handle) = spawn_podman_stub( + "lifecycle-stop", + vec![ + StubResponse::new(StatusCode::OK, r#"[{"Id":"ctr-1","State":"running"}]"#), + StubResponse::new(StatusCode::NO_CONTENT, ""), + ], + ); + test_driver(stop_socket.clone()) + .stop_sandbox("sandbox-1") + .await + .expect("stop should succeed"); + stop_handle.await.expect("stop stub should finish"); + assert_eq!( + stop_requests + .lock() + .expect("request log lock should not be poisoned")[1], + format!( + "POST {}", + api_path("/libpod/containers/ctr-1/stop?timeout=10") + ) + ); + + let (start_socket, start_requests, start_handle) = spawn_podman_stub( + "lifecycle-start", + vec![ + StubResponse::new(StatusCode::OK, r#"[{"Id":"ctr-1","State":"stopped"}]"#), + StubResponse::new(StatusCode::NO_CONTENT, ""), + ], + ); + test_driver(start_socket.clone()) + .resume_sandbox("sandbox-1") + .await + .expect("resume should succeed"); + start_handle.await.expect("start stub should finish"); + assert_eq!( + start_requests + .lock() + .expect("request log lock should not be poisoned")[1], + format!("POST {}", api_path("/libpod/containers/ctr-1/start")) + ); + + let _ = fs::remove_file(stop_socket); + let _ = fs::remove_file(start_socket); } #[test] diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index 2d0792d447..ea5d54ac28 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -8,9 +8,10 @@ use openshell_core::proto::compute::v1::{ CreateSandboxRequest, CreateSandboxResponse, DeleteSandboxRequest, DeleteSandboxResponse, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, - ListSandboxesRequest, ListSandboxesResponse, StopSandboxRequest, StopSandboxResponse, - ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, - WatchSandboxesRequest, compute_driver_server::ComputeDriver, + ListSandboxesRequest, ListSandboxesResponse, ResumeSandboxRequest, ResumeSandboxResponse, + StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, + ValidateSandboxCreateResponse, WatchSandboxesEvent, WatchSandboxesRequest, + compute_driver_server::ComputeDriver, }; use std::pin::Pin; use tonic::{Request, Response, Status}; @@ -127,6 +128,21 @@ impl ComputeDriver for ComputeDriverService { Ok(Response::new(StopSandboxResponse {})) } + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + if request.sandbox_id.is_empty() { + return Err(Status::invalid_argument("sandbox_id is required")); + } + self.driver + .resume_sandbox(&request.sandbox_id) + .await + .map_err(Status::from)?; + Ok(Response::new(ResumeSandboxResponse {})) + } + async fn delete_sandbox( &self, request: Request, @@ -181,6 +197,12 @@ mod tests { assert_eq!(status.code(), tonic::Code::AlreadyExists); } + #[test] + fn not_found_driver_errors_map_to_not_found_status() { + let status: Status = ComputeDriverError::NotFound.into(); + assert_eq!(status.code(), tonic::Code::NotFound); + } + fn test_service(socket_path: PathBuf) -> ComputeDriverService { let config = PodmanComputeConfig { socket_path: Some(socket_path), diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index 23f75227a7..a26e049c07 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -199,6 +199,13 @@ restarts each persisted VM launcher, and preserves any existing `overlay.ext4` instead of cloning a fresh overlay template. If a restart happened before the overlay was created, the driver creates it during the resume attempt. +Suspension writes a marker in the sandbox state directory before terminating +the launcher and releasing host GPU and network allocations. It retains +`sandbox.pb`, `overlay.ext4`, and lifecycle-extension state. Startup registers +marked sandboxes without launching compute. Resume removes the marker and uses +the normal persisted restore path with the existing overlay. Delete removes the +entire sandbox state directory, including a suspended marker and overlay. + ## Logs and debugging Raise log verbosity for both processes: diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index 9b6c0dd6ce..3a0ce9768c 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -43,10 +43,10 @@ use openshell_core::proto::compute::v1::{ DriverSandboxTemplate as SandboxTemplate, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, ListSandboxesRequest, ListSandboxesResponse, - StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, - ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, WatchSandboxesEvent, - WatchSandboxesPlatformEvent, WatchSandboxesRequest, WatchSandboxesSandboxEvent, - compute_driver_server::ComputeDriver, watch_sandboxes_event, + ResumeSandboxRequest, ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, + ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, + WatchSandboxesEvent, WatchSandboxesPlatformEvent, WatchSandboxesRequest, + WatchSandboxesSandboxEvent, compute_driver_server::ComputeDriver, watch_sandboxes_event, }; use openshell_core::proto_struct::{ deserialize_optional_non_empty_string_list, struct_to_json_value, @@ -166,6 +166,7 @@ const OVERLAY_TEMPLATE_CACHE_DIR: &str = "overlay-templates"; const OVERLAY_TEMPLATE_CACHE_LAYOUT_VERSION: &str = "sandbox-overlay-ext4-v1"; const SANDBOX_OVERLAY_IMAGE: &str = "overlay.ext4"; const SANDBOX_REQUEST_FILE: &str = "sandbox.pb"; +const SANDBOX_SUSPENDED_FILE: &str = "suspended"; const GUEST_IMAGE_CONFIG_DIR: &str = "openshell-image"; const GUEST_IMAGE_OCI_LAYOUT_DIR: &str = "oci"; const GUEST_IMAGE_OCI_REF: &str = "openshell"; @@ -1058,6 +1059,132 @@ impl VmDriver { Ok(()) } + pub async fn stop_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result<(), Status> { + if !sandbox_id.is_empty() { + validate_sandbox_id(sandbox_id)?; + } + let record_id = { + let registry = self.registry.lock().await; + if registry.contains_key(sandbox_id) { + Some(sandbox_id.to_string()) + } else { + registry + .iter() + .find(|(_, record)| record.snapshot.name == sandbox_name) + .map(|(id, _)| id.clone()) + } + } + .ok_or_else(|| Status::not_found("sandbox not found"))?; + + let state_dir = { + let registry = self.registry.lock().await; + registry + .get(&record_id) + .ok_or_else(|| Status::not_found("sandbox not found"))? + .state_dir + .clone() + }; + + // Persist intent before detaching process handles or releasing host + // allocations. If this write fails, the live record remains intact. + tokio::fs::write(state_dir.join(SANDBOX_SUSPENDED_FILE), b"suspended\n") + .await + .map_err(|err| Status::internal(format!("persist suspension marker failed: {err}")))?; + + let (process, provisioning_task, has_gpu, has_qemu_network, snapshot) = { + let mut registry = self.registry.lock().await; + let record = registry + .get_mut(&record_id) + .ok_or_else(|| Status::not_found("sandbox not found"))?; + ( + record.process.take(), + record.provisioning_task.take(), + record.gpu_bdf.take().is_some(), + std::mem::take(&mut record.qemu_network_allocated), + record.snapshot.clone(), + ) + }; + + if let Some(task) = provisioning_task { + task.abort(); + } + if let Some(process) = process { + let mut process = process.lock().await; + process.deleting = true; + terminate_vm_process(&mut process.child) + .await + .map_err(|err| Status::internal(format!("failed to stop vm: {err}")))?; + } + self.lifecycle_extensions + .after_launch_failed(&snapshot, &state_dir, LaunchAbortReason::Suspended) + .await; + self.release_allocations(&record_id, has_gpu, has_qemu_network); + + if let Some(snapshot) = self + .set_snapshot_condition(&record_id, suspended_condition(), false) + .await + { + self.publish_snapshot(snapshot); + } + self.publish_platform_event( + record_id, + platform_event( + "vm", + "Normal", + "Suspended", + "VM sandbox suspended".to_string(), + ), + ); + Ok(()) + } + + pub async fn resume_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result<(), Status> { + if !sandbox_id.is_empty() { + validate_sandbox_id(sandbox_id)?; + } + let (record_id, state_dir, already_running) = { + let registry = self.registry.lock().await; + let (id, record) = if let Some(entry) = registry.get_key_value(sandbox_id) { + entry + } else { + registry + .iter() + .find(|(_, record)| record.snapshot.name == sandbox_name) + .ok_or_else(|| Status::not_found("sandbox not found"))? + }; + ( + id.clone(), + record.state_dir.clone(), + record.process.is_some() || record.provisioning_task.is_some(), + ) + }; + if already_running { + return Ok(()); + } + + let sandbox = read_sandbox_request(&state_dir.join(SANDBOX_REQUEST_FILE)) + .await + .map_err(|err| { + Status::internal(format!("read sandbox resume metadata failed: {err}")) + })?; + match tokio::fs::remove_file(state_dir.join(SANDBOX_SUSPENDED_FILE)).await { + Ok(()) => {} + Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => { + return Err(Status::internal(format!( + "remove suspension marker failed: {err}" + ))); + } + } + self.registry.lock().await.remove(&record_id); + self.restore_persisted_sandbox(sandbox, state_dir, &tracing::Span::current()) + .await; + if !self.registry.lock().await.contains_key(&record_id) { + return Err(Status::internal("failed to resume persisted VM sandbox")); + } + Ok(()) + } + #[tracing::instrument( name = "vm.delete", skip(self), @@ -1264,6 +1391,27 @@ impl VmDriver { continue; } + if tokio::fs::metadata(state_dir.join(SANDBOX_SUSPENDED_FILE)) + .await + .is_ok() + { + let snapshot = sandbox_snapshot(&sandbox, suspended_condition(), false); + let mut registry = self.registry.lock().await; + registry.entry(sandbox.id.clone()).or_insert(SandboxRecord { + snapshot: snapshot.clone(), + state_dir: state_dir.clone(), + process: None, + provisioning_task: None, + gpu_bdf: None, + qemu_network_allocated: false, + deleting: false, + }); + drop(registry); + self.publish_snapshot(snapshot); + info!(sandbox_id = %sandbox.id, "vm driver: restored suspended sandbox without launching compute"); + continue; + } + self.restore_persisted_sandbox(sandbox, state_dir, &tracing::Span::current()) .await; } @@ -3179,11 +3327,22 @@ impl ComputeDriver for VmDriver { async fn stop_sandbox( &self, - _request: Request, + request: Request, ) -> Result, Status> { - Err(Status::unimplemented( - "stop sandbox is not implemented by the vm compute driver", - )) + let request = request.into_inner(); + self.stop_sandbox(&request.sandbox_id, &request.sandbox_name) + .await?; + Ok(Response::new(StopSandboxResponse {})) + } + + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + self.resume_sandbox(&request.sandbox_id, &request.sandbox_name) + .await?; + Ok(Response::new(ResumeSandboxResponse {})) } async fn delete_sandbox( @@ -5184,6 +5343,16 @@ fn deleting_condition() -> SandboxCondition { } } +fn suspended_condition() -> SandboxCondition { + SandboxCondition { + r#type: "Suspended".to_string(), + status: "True".to_string(), + reason: "ComputeStopped".to_string(), + message: "VM compute is stopped and persistent state is retained".to_string(), + last_transition_time: String::new(), + } +} + fn error_condition(reason: &str, message: &str) -> SandboxCondition { SandboxCondition { r#type: "Ready".to_string(), diff --git a/crates/openshell-driver-vm/src/lifecycle.rs b/crates/openshell-driver-vm/src/lifecycle.rs index 646070c3a1..5ab2ff01b8 100644 --- a/crates/openshell-driver-vm/src/lifecycle.rs +++ b/crates/openshell-driver-vm/src/lifecycle.rs @@ -32,6 +32,8 @@ pub enum LaunchAbortReason { /// opportunity to release host resources they allocated in /// [`LifecycleExtension::before_launch`]. ProcessExited, + /// The gateway intentionally suspended the sandbox while retaining disk state. + Suspended, } #[derive(Debug, Clone)] diff --git a/crates/openshell-driver-vm/src/otel_tracing.rs b/crates/openshell-driver-vm/src/otel_tracing.rs index fac2720cd7..df950b026c 100644 --- a/crates/openshell-driver-vm/src/otel_tracing.rs +++ b/crates/openshell-driver-vm/src/otel_tracing.rs @@ -74,6 +74,7 @@ fn compute_driver_rpc_operation(path: &str) -> (&'static str, &'static str) { Some("GetSandbox") => ("driver.get_sandbox", "get_sandbox"), Some("ListSandboxes") => ("driver.list_sandboxes", "list_sandboxes"), Some("StopSandbox") => ("driver.stop_sandbox", "stop_sandbox"), + Some("ResumeSandbox") => ("driver.resume_sandbox", "resume_sandbox"), Some("DeleteSandbox") => ("driver.delete_sandbox", "delete_sandbox"), Some("WatchSandboxes") => ("driver.watch_sandboxes", "watch_sandboxes"), _ => ("driver.unknown", "unknown"), @@ -177,6 +178,7 @@ mod tests { ("GetSandbox", "driver.get_sandbox", "get_sandbox"), ("ListSandboxes", "driver.list_sandboxes", "list_sandboxes"), ("StopSandbox", "driver.stop_sandbox", "stop_sandbox"), + ("ResumeSandbox", "driver.resume_sandbox", "resume_sandbox"), ("DeleteSandbox", "driver.delete_sandbox", "delete_sandbox"), ( "WatchSandboxes", diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index 0924ae4d62..b2c08ecf3a 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -217,6 +217,34 @@ impl OpenShellClient { Ok(response.deleted) } + /// Suspend a sandbox by name. + pub async fn suspend_sandbox(&self, name: &str) -> Result { + let response = self + .unary(|mut grpc| { + let request = proto::SuspendSandboxRequest { + name: name.to_string(), + workspace: String::new(), + }; + async move { grpc.suspend_sandbox(request).await } + }) + .await?; + sandbox_from_response(response.sandbox) + } + + /// Resume a suspended sandbox by name. + pub async fn resume_sandbox(&self, name: &str) -> Result { + let response = self + .unary(|mut grpc| { + let request = proto::ResumeSandboxRequest { + name: name.to_string(), + workspace: String::new(), + }; + async move { grpc.resume_sandbox(request).await } + }) + .await?; + sandbox_from_response(response.sandbox) + } + /// Poll [`OpenShellClient::get_sandbox`] until the sandbox reaches /// [`SandboxPhase::Ready`] or the `timeout` elapses. /// @@ -586,6 +614,36 @@ impl WorkspaceScopedClient { Ok(response.deleted) } + /// Suspend a sandbox by name in this workspace. + pub async fn suspend_sandbox(&self, name: &str) -> Result { + let response = self + .client + .unary(|mut grpc| { + let request = proto::SuspendSandboxRequest { + name: name.to_string(), + workspace: self.workspace.clone(), + }; + async move { grpc.suspend_sandbox(request).await } + }) + .await?; + sandbox_from_response(response.sandbox) + } + + /// Resume a suspended sandbox by name in this workspace. + pub async fn resume_sandbox(&self, name: &str) -> Result { + let response = self + .client + .unary(|mut grpc| { + let request = proto::ResumeSandboxRequest { + name: name.to_string(), + workspace: self.workspace.clone(), + }; + async move { grpc.resume_sandbox(request).await } + }) + .await?; + sandbox_from_response(response.sandbox) + } + /// Poll until the sandbox reaches [`SandboxPhase::Ready`] or the timeout /// elapses. pub async fn wait_ready(&self, name: &str, timeout: Duration) -> Result { diff --git a/crates/openshell-sdk/src/raw.rs b/crates/openshell-sdk/src/raw.rs index 80d7453602..4619d3d743 100644 --- a/crates/openshell-sdk/src/raw.rs +++ b/crates/openshell-sdk/src/raw.rs @@ -24,9 +24,9 @@ pub use openshell_core::proto::open_shell_client::OpenShellClient as GrpcClient; pub use openshell_core::proto::{ CreateSandboxRequest, CreateWorkspaceRequest, DeleteSandboxRequest, DeleteWorkspaceRequest, ExecSandboxRequest, GetSandboxRequest, GetWorkspaceRequest, HealthRequest, - ListProvidersRequest, ListSandboxesRequest, ListWorkspacesRequest, Sandbox, - SandboxPhase as ProtoSandboxPhase, SandboxSpec as ProtoSandboxSpec, SandboxTemplate, - ServiceStatus as ProtoServiceStatus, Workspace, + ListProvidersRequest, ListSandboxesRequest, ListWorkspacesRequest, ResumeSandboxRequest, + Sandbox, SandboxPhase as ProtoSandboxPhase, SandboxSpec as ProtoSandboxSpec, SandboxTemplate, + ServiceStatus as ProtoServiceStatus, SuspendSandboxRequest, Workspace, }; /// Type alias for the gRPC client wrapped in the SDK's auth interceptor. diff --git a/crates/openshell-sdk/src/types.rs b/crates/openshell-sdk/src/types.rs index 2eb4ef0a92..987201f33b 100644 --- a/crates/openshell-sdk/src/types.rs +++ b/crates/openshell-sdk/src/types.rs @@ -62,6 +62,9 @@ pub enum SandboxPhase { Error, Deleting, Unknown, + Suspending, + Suspended, + Resuming, } impl From for SandboxPhase { @@ -73,6 +76,9 @@ impl From for SandboxPhase { proto::SandboxPhase::Error => Self::Error, proto::SandboxPhase::Deleting => Self::Deleting, proto::SandboxPhase::Unknown => Self::Unknown, + proto::SandboxPhase::Suspending => Self::Suspending, + proto::SandboxPhase::Suspended => Self::Suspended, + proto::SandboxPhase::Resuming => Self::Resuming, } } } diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 4ff23d74d1..aff6bf6829 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -31,6 +31,8 @@ struct MockState { last_create: Mutex>, last_delete_name: Mutex>, last_delete_workspace: Mutex>, + last_suspend: Mutex>, + last_resume: Mutex>, last_list_request: Mutex>, last_exec_request: Mutex>, last_workspace_request: Mutex>, @@ -161,6 +163,38 @@ impl OpenShell for TestOpenShell { })) } + async fn suspend_sandbox( + &self, + request: tonic::Request, + ) -> Result, Status> { + let request = request.into_inner(); + let sandbox = sandbox_with_phase_ws( + &request.name, + proto::SandboxPhase::Suspended, + &request.workspace, + ); + *self.state.last_suspend.lock().await = Some(request); + Ok(Response::new(proto::SandboxResponse { + sandbox: Some(sandbox), + })) + } + + async fn resume_sandbox( + &self, + request: tonic::Request, + ) -> Result, Status> { + let request = request.into_inner(); + let sandbox = sandbox_with_phase_ws( + &request.name, + proto::SandboxPhase::Resuming, + &request.workspace, + ); + *self.state.last_resume.lock().await = Some(request); + Ok(Response::new(proto::SandboxResponse { + sandbox: Some(sandbox), + })) + } + async fn get_sandbox( &self, request: tonic::Request, @@ -816,6 +850,29 @@ async fn delete_sandbox_returns_server_ack() { assert_eq!(observed.as_deref(), Some("doomed")); } +#[tokio::test] +async fn suspend_and_resume_map_requests_and_phases() { + let state = Arc::new(MockState::default()); + let endpoint = start_mock(state.clone()).await; + let client = connect(&endpoint).await; + + let suspended = client.suspend_sandbox("sleepy").await.unwrap(); + assert_eq!(suspended.phase, SandboxPhase::Suspended); + let suspend = state.last_suspend.lock().await.clone().unwrap(); + assert_eq!(suspend.name, "sleepy"); + assert!(suspend.workspace.is_empty()); + + let resumed = client + .workspace("team-a") + .resume_sandbox("sleepy") + .await + .unwrap(); + assert_eq!(resumed.phase, SandboxPhase::Resuming); + let resume = state.last_resume.lock().await.clone().unwrap(); + assert_eq!(resume.name, "sleepy"); + assert_eq!(resume.workspace, "team-a"); +} + #[tokio::test] async fn wait_ready_transitions_through_phases() { let state = Arc::new(MockState { diff --git a/crates/openshell-server/src/auth/method_authz.rs b/crates/openshell-server/src/auth/method_authz.rs index c06ca09f69..557e334e21 100644 --- a/crates/openshell-server/src/auth/method_authz.rs +++ b/crates/openshell-server/src/auth/method_authz.rs @@ -148,4 +148,18 @@ mod tests { // Unknown method falls through to AuthzPolicy::check. assert!(is_user_callable("/openshell.v1.OpenShell/FutureMethod")); } + + #[test] + fn sandbox_lifecycle_mutations_require_user_write_authority() { + for path in [ + "/openshell.v1.OpenShell/SuspendSandbox", + "/openshell.v1.OpenShell/ResumeSandbox", + ] { + let entry = lookup(path).expect("lifecycle RPC must have auth metadata"); + assert_eq!(entry.auth_mode, AuthMode::Bearer); + assert_eq!(entry.scope.as_deref(), Some("sandbox:write")); + assert_eq!(entry.workspace_role.as_deref(), Some("user")); + assert!(!is_sandbox_callable(path)); + } + } } diff --git a/crates/openshell-server/src/auth/sandbox_methods.rs b/crates/openshell-server/src/auth/sandbox_methods.rs index a74b1280ce..9fb46c68fd 100644 --- a/crates/openshell-server/src/auth/sandbox_methods.rs +++ b/crates/openshell-server/src/auth/sandbox_methods.rs @@ -42,6 +42,12 @@ mod tests { assert!(!is_sandbox_callable( "/openshell.v1.OpenShell/DeleteSandbox" )); + assert!(!is_sandbox_callable( + "/openshell.v1.OpenShell/SuspendSandbox" + )); + assert!(!is_sandbox_callable( + "/openshell.v1.OpenShell/ResumeSandbox" + )); assert!(!is_sandbox_callable( "/openshell.v1.OpenShell/CreateProvider" )); diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index a1c33e49ff..b76c5bf409 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -32,10 +32,10 @@ use openshell_core::proto::compute::v1::{ GetCapabilitiesRequest, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GpuResourceRequirements as DriverGpuResourceRequirements, ListSandboxesRequest, - ResourceRequirements as DriverSandboxResourceRequirements, ValidateSandboxCreateRequest, - WatchSandboxesEvent, WatchSandboxesRequest, compute_driver_client::ComputeDriverClient, - compute_driver_server::ComputeDriver, gateway_listener_requirement::Selector, - watch_sandboxes_event, + ResourceRequirements as DriverSandboxResourceRequirements, ResumeSandboxRequest, + StopSandboxRequest, ValidateSandboxCreateRequest, WatchSandboxesEvent, WatchSandboxesRequest, + compute_driver_client::ComputeDriverClient, compute_driver_server::ComputeDriver, + gateway_listener_requirement::Selector, watch_sandboxes_event, }; use openshell_core::proto::{ PlatformEvent, Sandbox, SandboxCondition, SandboxPhase, SandboxSpec, SandboxStatus, @@ -168,20 +168,20 @@ impl GatewayListenerRequirement { } } -/// Serializes request-side deletes for the same stable sandbox ID. +/// Serializes request-side lifecycle mutations for the same stable sandbox ID. /// /// Watch events deliberately do not use these gates, so a slow driver delete /// cannot block the sequential watch loop. Weak values let entries disappear /// after the last request using a sandbox's gate completes. #[derive(Debug, Default)] -struct DeleteGateRegistry { +struct LifecycleGateRegistry { gates: StdMutex>>>, } -impl DeleteGateRegistry { - async fn lock_for(&self, sandbox_id: &str) -> SandboxDeleteGuard { +impl LifecycleGateRegistry { + async fn lock_for(&self, sandbox_id: &str) -> SandboxLifecycleGuard { let gate = self.gate_for(sandbox_id); - SandboxDeleteGuard { + SandboxLifecycleGuard { _guard: gate.lock_owned().await, } } @@ -190,7 +190,7 @@ impl DeleteGateRegistry { let mut gates = self .gates .lock() - .expect("sandbox delete gate registry lock poisoned"); + .expect("sandbox lifecycle gate registry lock poisoned"); gates.retain(|_, gate| gate.strong_count() > 0); if let Some(gate) = gates.get(sandbox_id).and_then(Weak::upgrade) { @@ -206,18 +206,18 @@ impl DeleteGateRegistry { fn entry_count(&self) -> usize { self.gates .lock() - .expect("sandbox delete gate registry lock poisoned") + .expect("sandbox lifecycle gate registry lock poisoned") .len() } } -/// Proof that the current delete operation holds its sandbox-ID gate. +/// Proof that the current operation holds its sandbox-ID lifecycle gate. /// -/// Delete code must acquire this guard before taking `ComputeRuntime::sync_lock`. -/// Passing it to `lock_global_for_delete` makes that ordering visible at every -/// global-lock acquisition in the delete path. +/// Lifecycle code must acquire this guard before taking `ComputeRuntime::sync_lock`. +/// Passing it to `lock_global_for_lifecycle` makes that ordering visible at +/// every global-lock acquisition in a lifecycle path. #[derive(Debug)] -struct SandboxDeleteGuard { +struct SandboxLifecycleGuard { _guard: tokio::sync::OwnedMutexGuard<()>, } @@ -517,13 +517,22 @@ impl ComputeDriver for RemoteComputeDriver { async fn stop_sandbox( &self, - request: Request, + request: Request, ) -> Result, Status> { let mut client = self.client(); client.stop_sandbox(request).await } + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> + { + let mut client = self.client(); + client.resume_sandbox(request).await + } + async fn delete_sandbox( &self, request: Request, @@ -558,7 +567,7 @@ pub struct ComputeRuntime { tracing_log_bus: TracingLogBus, supervisor_sessions: Arc, sync_lock: Arc>, - delete_gates: Arc, + lifecycle_gates: Arc, gateway_listener_requirements: Vec, replica_id: String, } @@ -679,7 +688,7 @@ impl ComputeRuntime { tracing_log_bus, supervisor_sessions, sync_lock: Arc::new(Mutex::new(())), - delete_gates: Arc::new(DeleteGateRegistry::default()), + lifecycle_gates: Arc::new(LifecycleGateRegistry::default()), gateway_listener_requirements, replica_id: lease::replica_id(), }) @@ -696,18 +705,18 @@ impl ComputeRuntime { } /// Acquires the process-wide lock for code that already holds the - /// sandbox-ID delete gate. The guard parameter documents and enforces that - /// delete-path callers acquire locks in delete-gate -> global-lock order. - async fn lock_global_for_delete( + /// sandbox-ID lifecycle gate. The guard parameter documents and enforces + /// that callers acquire locks in lifecycle-gate -> global-lock order. + async fn lock_global_for_lifecycle( &self, - _delete_guard: &SandboxDeleteGuard, + _lifecycle_guard: &SandboxLifecycleGuard, ) -> tokio::sync::OwnedMutexGuard<()> { self.sync_lock.clone().lock_owned().await } #[cfg(test)] - pub(crate) fn delete_gate_entry_count(&self) -> usize { - self.delete_gates.entry_count() + pub(crate) fn lifecycle_gate_entry_count(&self) -> usize { + self.lifecycle_gates.entry_count() } pub async fn new_docker( @@ -963,6 +972,266 @@ impl ComputeRuntime { } } + pub(crate) async fn suspend_sandbox( + &self, + workspace: &str, + name: &str, + ) -> Result { + let candidate = self + .store + .get_message_by_name::(workspace, name) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + let sandbox_id = candidate.object_id().to_string(); + let sandbox_name = candidate.object_name().to_string(); + let lifecycle_guard = self.lifecycle_gates.lock_for(&sandbox_id).await; + let global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; + let current = self + .store + .get_message::(&sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + if current.object_name() != sandbox_name { + return Err(Status::aborted( + "sandbox name changed while the suspend request was waiting; retry explicitly", + )); + } + + let phase = SandboxPhase::try_from(current.phase()).unwrap_or(SandboxPhase::Unknown); + if phase == SandboxPhase::Suspended { + return Ok(current); + } + if phase == SandboxPhase::Suspending { + return Ok(current); + } + if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Suspending) { + return Err(Status::failed_precondition(format!( + "sandbox must be Ready to suspend (current phase: {phase:?})" + ))); + } + + let previous = current.clone(); + let suspending = self + .write_lifecycle_phase( + ¤t, + SandboxPhase::Suspending, + "Suspending", + "Sandbox suspension requested", + ) + .await?; + self.sandbox_index.update_from_sandbox(&suspending); + self.sandbox_watch_bus.notify(&sandbox_id); + drop(global_guard); + + let result = self + .driver + .call("driver.stop_sandbox", Some(&sandbox_id), |driver| { + let sandbox_id = sandbox_id.clone(); + let sandbox_name = sandbox_name.clone(); + async move { + driver + .stop_sandbox(Request::new(StopSandboxRequest { + sandbox_id, + sandbox_name, + })) + .await + } + }) + .await; + + let _global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; + match result { + Ok(_) => { + let latest = self + .store + .get_message::(&sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + let phase = SandboxPhase::try_from(latest.phase()).unwrap_or(SandboxPhase::Unknown); + let suspended = if phase == SandboxPhase::Suspended { + latest + } else if phase == SandboxPhase::Suspending { + self.write_lifecycle_phase( + &latest, + SandboxPhase::Suspended, + "Suspended", + "Sandbox compute is stopped", + ) + .await? + } else { + return Err(Status::aborted( + "sandbox lifecycle changed while suspension completed", + )); + }; + self.cleanup_sandbox_ssh_sessions(&sandbox_id, workspace) + .await + .map_err(Status::internal)?; + self.supervisor_sessions.disconnect(&sandbox_id); + self.sandbox_index.update_from_sandbox(&suspended); + self.sandbox_watch_bus.notify(&sandbox_id); + Ok(suspended) + } + Err(err) => { + self.restore_lifecycle_snapshot(&suspending, &previous) + .await; + Err(Status::new( + err.code(), + format!("suspend sandbox failed: {}", err.message()), + )) + } + } + } + + pub(crate) async fn resume_sandbox( + &self, + workspace: &str, + name: &str, + ) -> Result { + let candidate = self + .store + .get_message_by_name::(workspace, name) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + let sandbox_id = candidate.object_id().to_string(); + let sandbox_name = candidate.object_name().to_string(); + let lifecycle_guard = self.lifecycle_gates.lock_for(&sandbox_id).await; + let global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; + let current = self + .store + .get_message::(&sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + if current.object_name() != sandbox_name { + return Err(Status::aborted( + "sandbox name changed while the resume request was waiting; retry explicitly", + )); + } + + let phase = SandboxPhase::try_from(current.phase()).unwrap_or(SandboxPhase::Unknown); + if phase == SandboxPhase::Ready { + return Ok(current); + } + if phase == SandboxPhase::Resuming { + return Ok(current); + } + if !matches!(phase, SandboxPhase::Suspended | SandboxPhase::Resuming) { + return Err(Status::failed_precondition(format!( + "sandbox must be Suspended to resume (current phase: {phase:?})" + ))); + } + + let previous = current.clone(); + let resuming = self + .write_lifecycle_phase( + ¤t, + SandboxPhase::Resuming, + "Resuming", + "Sandbox resume requested", + ) + .await?; + self.sandbox_index.update_from_sandbox(&resuming); + self.sandbox_watch_bus.notify(&sandbox_id); + drop(global_guard); + + let result = self + .driver + .call("driver.resume_sandbox", Some(&sandbox_id), |driver| { + let sandbox_id = sandbox_id.clone(); + let sandbox_name = sandbox_name.clone(); + async move { + driver + .resume_sandbox(Request::new(ResumeSandboxRequest { + sandbox_id, + sandbox_name, + })) + .await + } + }) + .await; + + let _global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; + match result { + Ok(_) => { + let latest = self + .store + .get_message::(&sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + Ok(latest) + } + Err(err) => { + self.restore_lifecycle_snapshot(&resuming, &previous).await; + Err(Status::new( + err.code(), + format!("resume sandbox failed: {}", err.message()), + )) + } + } + } + + async fn write_lifecycle_phase( + &self, + sandbox: &Sandbox, + phase: SandboxPhase, + reason: &str, + message: &str, + ) -> Result { + let sandbox_id = sandbox.object_id().to_string(); + let expected_resource_version = sandbox_resource_version(sandbox); + let reason = reason.to_string(); + let message = message.to_string(); + self.store + .update_message_cas::( + &sandbox_id, + expected_resource_version, + move |sandbox| { + sandbox.set_phase(phase as i32); + let name = sandbox.object_name().to_string(); + upsert_ready_condition( + &mut sandbox.status, + &name, + SandboxCondition { + r#type: "Ready".to_string(), + status: "False".to_string(), + reason: reason.clone(), + message: message.clone(), + last_transition_time: String::new(), + }, + ); + }, + ) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "update sandbox lifecycle")) + } + + async fn restore_lifecycle_snapshot(&self, owned: &Sandbox, previous: &Sandbox) { + let sandbox_id = owned.object_id().to_string(); + let previous = previous.clone(); + match self + .store + .update_message_cas::( + &sandbox_id, + sandbox_resource_version(owned), + move |sandbox| *sandbox = previous.clone(), + ) + .await + { + Ok(restored) => { + self.sandbox_index.update_from_sandbox(&restored); + self.sandbox_watch_bus.notify(&sandbox_id); + } + Err(err) => { + debug!(sandbox_id, error = %err, "Skipped lifecycle rollback after concurrent change"); + } + } + } + pub(crate) async fn delete_sandbox( &self, workspace: &str, @@ -981,8 +1250,8 @@ impl ComputeRuntime { sandbox_id: candidate.object_id().to_string(), sandbox_name: candidate.object_name().to_string(), }; - let delete_guard = self.delete_gates.lock_for(&target.sandbox_id).await; - let global_guard = self.lock_global_for_delete(&delete_guard).await; + let delete_guard = self.lifecycle_gates.lock_for(&target.sandbox_id).await; + let global_guard = self.lock_global_for_lifecycle(&delete_guard).await; // There is no await between acquiring the initial guards and spawning // the worker. From this commitment point onward, request cancellation @@ -1010,7 +1279,7 @@ impl ComputeRuntime { async fn delete_sandbox_inner( &self, target: SandboxDeleteTarget, - delete_guard: SandboxDeleteGuard, + delete_guard: SandboxLifecycleGuard, guard: tokio::sync::OwnedMutexGuard<()>, ) -> Result { let current = self @@ -1176,10 +1445,10 @@ impl ComputeRuntime { /// row leaves `Deleting`; that state belongs to a concurrent writer. async fn remove_deleting_sandbox_record( &self, - delete_guard: &SandboxDeleteGuard, + delete_guard: &SandboxLifecycleGuard, sandbox_id: &str, ) -> bool { - let _guard = self.lock_global_for_delete(delete_guard).await; + let _guard = self.lock_global_for_lifecycle(delete_guard).await; for attempt in 1..=DELETE_PHASE_CAS_RETRY_LIMIT { let record = match self.store.get(Sandbox::object_type(), sandbox_id).await { Ok(Some(record)) => record, @@ -1310,7 +1579,7 @@ impl ComputeRuntime { /// backend, or restore the pre-delete snapshot when lookup is inconclusive. async fn recover_failed_delete( &self, - delete_guard: &SandboxDeleteGuard, + delete_guard: &SandboxLifecycleGuard, transition: &DeleteTransition, ) { let sandbox_id = transition.deleting.object_id(); @@ -1319,7 +1588,7 @@ impl ComputeRuntime { // The driver lookup is deliberately outside the process-wide guard. let observed = self.get_driver_sandbox(sandbox_id, sandbox_name).await; - let _guard = self.lock_global_for_delete(delete_guard).await; + let _guard = self.lock_global_for_lifecycle(delete_guard).await; match observed { Ok(Some(snapshot)) if snapshot.id == sandbox_id && snapshot.status.is_some() => { @@ -1581,6 +1850,7 @@ impl ComputeRuntime { /// Should be called once at gateway startup, before watchers spawn, /// so the watch loop sees the post-resume state on its first poll. pub async fn resume_persisted_sandboxes(&self) -> Result<(), String> { + self.recover_persisted_lifecycle_transitions().await?; let Some(resume) = &self.startup_resume else { return Ok(()); }; @@ -1670,6 +1940,93 @@ impl ComputeRuntime { Ok(()) } + async fn recover_persisted_lifecycle_transitions(&self) -> Result<(), String> { + let records = self + .store + .list_by_type(Sandbox::object_type(), 1000, 0) + .await + .map_err(|e| e.to_string())?; + for record in records { + let sandbox = match Sandbox::decode(record.payload.as_slice()) { + Ok(sandbox) => sandbox, + Err(err) => { + warn!(error = %err, "Failed to decode sandbox during lifecycle recovery"); + continue; + } + }; + let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); + match phase { + SandboxPhase::Suspending => { + let sandbox_id = sandbox.object_id().to_string(); + let sandbox_name = sandbox.object_name().to_string(); + let driver_sandbox_id = sandbox_id.clone(); + match self + .driver + .call( + "driver.stop_sandbox", + Some(&sandbox_id), + |driver| async move { + driver + .stop_sandbox(Request::new(StopSandboxRequest { + sandbox_id: driver_sandbox_id, + sandbox_name, + })) + .await + }, + ) + .await + { + Ok(_) => match self + .write_lifecycle_phase( + &sandbox, + SandboxPhase::Suspended, + "Suspended", + "Sandbox compute is stopped", + ) + .await + { + Ok(updated) => { + self.sandbox_index.update_from_sandbox(&updated); + self.sandbox_watch_bus.notify(updated.object_id()); + } + Err(err) => { + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to persist recovered suspension"); + } + }, + Err(err) => { + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to recover sandbox suspension"); + } + } + } + SandboxPhase::Resuming => { + let sandbox_id = sandbox.object_id().to_string(); + let sandbox_name = sandbox.object_name().to_string(); + let driver_sandbox_id = sandbox_id.clone(); + if let Err(err) = self + .driver + .call( + "driver.resume_sandbox", + Some(&sandbox_id), + |driver| async move { + driver + .resume_sandbox(Request::new(ResumeSandboxRequest { + sandbox_id: driver_sandbox_id, + sandbox_name, + })) + .await + }, + ) + .await + { + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to recover sandbox resume"); + } + } + _ => {} + } + } + Ok(()) + } + async fn mark_sandbox_error(&self, sandbox: &Sandbox, reason: &str, message: &str) { let _guard = self.sync_lock.lock().await; let sandbox_id = sandbox.object_id().to_string(); @@ -2101,7 +2458,13 @@ impl ComputeRuntime { }; let current_phase = SandboxPhase::try_from(existing.phase()).unwrap_or(SandboxPhase::Unknown); - if current_phase == SandboxPhase::Deleting || current_phase == SandboxPhase::Error { + if matches!( + current_phase, + SandboxPhase::Deleting + | SandboxPhase::Error + | SandboxPhase::Suspending + | SandboxPhase::Suspended + ) { return Ok(()); } if !connected && current_phase != SandboxPhase::Ready { @@ -2270,10 +2633,10 @@ impl ComputeRuntime { async fn cleanup_local_state_if_sandbox_absent( &self, - delete_guard: &SandboxDeleteGuard, + delete_guard: &SandboxLifecycleGuard, sandbox_id: &str, ) -> Result<(), Status> { - let _guard = self.lock_global_for_delete(delete_guard).await; + let _guard = self.lock_global_for_lifecycle(delete_guard).await; let record = self .store .get(Sandbox::object_type(), sandbox_id) @@ -2405,6 +2768,45 @@ impl ComputeRuntime { } let sandbox = decode_sandbox_record(¤t_record)?; + let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); + if matches!( + phase, + SandboxPhase::Suspending | SandboxPhase::Suspended | SandboxPhase::Resuming + ) { + let updated = self + .store + .update_message_cas::( + &sandbox_id, + expected_resource_version, + |sandbox| { + sandbox.set_phase(SandboxPhase::Error as i32); + let name = sandbox.object_name().to_string(); + upsert_ready_condition( + &mut sandbox.status, + &name, + SandboxCondition { + r#type: "Ready".to_string(), + status: "False".to_string(), + reason: "ComputeResourceMissing".to_string(), + message: "The compute driver could not find the retained sandbox resource; delete the sandbox to clean up its remaining state" + .to_string(), + last_transition_time: String::new(), + }, + ); + }, + ) + .await + .map_err(|err| err.to_string())?; + warn!( + sandbox_id = %sandbox_id, + sandbox_name = %sandbox_name, + phase = ?phase, + "Retained sandbox resource disappeared from the compute driver" + ); + self.sandbox_index.update_from_sandbox(&updated); + self.sandbox_watch_bus.notify(&sandbox_id); + return Ok(()); + } info!( sandbox_id = %sandbox_id, sandbox_name = %sandbox_name, @@ -2811,7 +3213,7 @@ fn apply_driver_snapshot(sandbox: &mut Sandbox, incoming: &DriverSandbox, sessio let sandbox_name = &incoming.name; let cpv = sandbox.current_policy_version(); - let (phase, mut status) = incoming.status.as_ref().map_or_else( + let (mut phase, mut status) = incoming.status.as_ref().map_or_else( || { let mut phase = old_phase; let supervisor_promoted = session_connected @@ -2839,6 +3241,24 @@ fn apply_driver_snapshot(sandbox: &mut Sandbox, incoming: &DriverSandbox, sessio }, ); + phase = match old_phase { + SandboxPhase::Suspending + if phase == SandboxPhase::Suspended || driver_snapshot_confirms_stopped(incoming) => + { + SandboxPhase::Suspended + } + SandboxPhase::Suspending if phase != SandboxPhase::Error => SandboxPhase::Suspending, + SandboxPhase::Suspended => SandboxPhase::Suspended, + SandboxPhase::Resuming if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Error) => { + SandboxPhase::Resuming + } + _ => phase, + }; + + if let Some(status) = status.as_mut() { + status.phase = phase as i32; + } + if let Some(status) = status.as_mut() && status.sandbox_name.is_empty() { @@ -2882,6 +3302,18 @@ fn apply_driver_snapshot(sandbox: &mut Sandbox, incoming: &DriverSandbox, sessio sandbox.set_current_policy_version(cpv); } +fn driver_snapshot_confirms_stopped(incoming: &DriverSandbox) -> bool { + incoming.status.as_ref().is_some_and(|status| { + status.conditions.iter().any(|condition| { + condition.status.eq_ignore_ascii_case("false") + && matches!( + condition.reason.to_ascii_lowercase().as_str(), + "containerexited" | "containerstopped" + ) + }) + }) +} + fn ensure_supervisor_ready_status(status: &mut Option, sandbox_name: &str) { upsert_ready_condition( status, @@ -2916,7 +3348,7 @@ impl ComposedPhase { // before this driver snapshot arrived. Keep Ready rather than letting a lagging // backend phase overwrite it. let phase = match backend_phase { - SandboxPhase::Error | SandboxPhase::Deleting => backend_phase, + SandboxPhase::Error | SandboxPhase::Deleting | SandboxPhase::Suspended => backend_phase, _ if session_connected => SandboxPhase::Ready, _ => SandboxPhase::Provisioning, }; @@ -3019,6 +3451,13 @@ fn derive_phase(status: Option<&DriverSandboxStatus>) -> SandboxPhase { return SandboxPhase::Deleting; } + if status.conditions.iter().any(|condition| { + condition.r#type.eq_ignore_ascii_case("Suspended") + && condition.status.eq_ignore_ascii_case("true") + }) { + return SandboxPhase::Suspended; + } + for condition in &status.conditions { if condition.r#type == "Ready" { return if condition.status.eq_ignore_ascii_case("true") { @@ -3072,6 +3511,7 @@ fn sandbox_phase_should_be_running(phase: SandboxPhase) -> bool { SandboxPhase::Unspecified | SandboxPhase::Provisioning | SandboxPhase::Ready + | SandboxPhase::Resuming | SandboxPhase::Unknown ) } @@ -3167,7 +3607,7 @@ impl ComputeDriver for NoopTestDriver { async fn stop_sandbox( &self, - _request: Request, + _request: Request, ) -> Result, Status> { Ok(tonic::Response::new( @@ -3175,6 +3615,16 @@ impl ComputeDriver for NoopTestDriver { )) } + async fn resume_sandbox( + &self, + _request: Request, + ) -> Result, Status> + { + Ok(tonic::Response::new( + openshell_core::proto::compute::v1::ResumeSandboxResponse {}, + )) + } + async fn delete_sandbox( &self, _request: Request, @@ -3217,7 +3667,7 @@ pub async fn new_test_runtime_for_driver(store: Arc, driver_name: &str) - tracing_log_bus: TracingLogBus::new(), supervisor_sessions: Arc::new(SupervisorSessionRegistry::new()), sync_lock: Arc::new(Mutex::new(())), - delete_gates: Arc::new(DeleteGateRegistry::default()), + lifecycle_gates: Arc::new(LifecycleGateRegistry::default()), gateway_listener_requirements: Vec::new(), replica_id: "test-replica".to_string(), } @@ -3229,8 +3679,8 @@ mod tests { use futures::stream; use openshell_core::proto::compute::v1::{ CreateSandboxResponse, DeleteSandboxResponse, GetCapabilitiesResponse, GetSandboxRequest, - GetSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateResponse, - WatchSandboxesDeletedEvent, WatchSandboxesSandboxEvent, + GetSandboxResponse, ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, + ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, WatchSandboxesSandboxEvent, }; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -3446,6 +3896,13 @@ mod tests { Ok(tonic::Response::new(StopSandboxResponse {})) } + async fn resume_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + Ok(tonic::Response::new(ResumeSandboxResponse {})) + } + async fn delete_sandbox( &self, _request: Request, @@ -3485,6 +3942,8 @@ mod tests { delete_blocked: AtomicBool, delete_calls: AtomicUsize, delete_outcome: TestMutex, + stop_calls: AtomicUsize, + resume_calls: AtomicUsize, get_started: Notify, get_release: Semaphore, get_blocked: AtomicBool, @@ -3503,6 +3962,8 @@ mod tests { delete_blocked: AtomicBool::new(false), delete_calls: AtomicUsize::new(0), delete_outcome: TestMutex::new(ControlledDeleteOutcome::Ok(true)), + stop_calls: AtomicUsize::new(0), + resume_calls: AtomicUsize::new(0), get_started: Notify::new(), get_release: Semaphore::new(0), get_blocked: AtomicBool::new(false), @@ -3541,6 +4002,14 @@ mod tests { self.delete_calls.load(Ordering::SeqCst) } + fn stop_calls(&self) -> usize { + self.stop_calls.load(Ordering::SeqCst) + } + + fn resume_calls(&self) -> usize { + self.resume_calls.load(Ordering::SeqCst) + } + fn send_event(&self, event: WatchSandboxesEvent) { self.watch_tx .send(Ok(event)) @@ -3632,9 +4101,18 @@ mod tests { &self, _request: Request, ) -> Result, Status> { + self.stop_calls.fetch_add(1, Ordering::SeqCst); Ok(tonic::Response::new(StopSandboxResponse {})) } + async fn resume_sandbox( + &self, + _request: Request, + ) -> Result, Status> { + self.resume_calls.fetch_add(1, Ordering::SeqCst); + Ok(tonic::Response::new(ResumeSandboxResponse {})) + } + async fn delete_sandbox( &self, _request: Request, @@ -3704,7 +4182,7 @@ mod tests { tracing_log_bus: TracingLogBus::new(), supervisor_sessions: Arc::new(SupervisorSessionRegistry::new()), sync_lock: Arc::new(Mutex::new(())), - delete_gates: Arc::new(DeleteGateRegistry::default()), + lifecycle_gates: Arc::new(LifecycleGateRegistry::default()), gateway_listener_requirements: Vec::new(), replica_id: "test-replica".to_string(), } @@ -3992,7 +4470,7 @@ mod tests { #[test] fn delete_gate_registry_removes_stale_entries() { - let registry = DeleteGateRegistry::default(); + let registry = LifecycleGateRegistry::default(); let first = registry.gate_for("sb-1"); assert_eq!(registry.entry_count(), 1); drop(first); @@ -4422,6 +4900,13 @@ mod tests { self.0.stop_sandbox(request).await } + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + self.0.resume_sandbox(request).await + } + async fn delete_sandbox( &self, request: Request, @@ -4467,6 +4952,165 @@ mod tests { ); } + #[tokio::test] + async fn suspend_and_resume_follow_durable_state_machine() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let sandbox = sandbox_record("sb-lifecycle", "sandbox-lifecycle", SandboxPhase::Ready); + runtime.store.put_message(&sandbox).await.unwrap(); + let session = ssh_session_record("lifecycle-session", sandbox.object_id()); + runtime.store.put_message(&session).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + + let suspended = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + assert_eq!(suspended.phase(), SandboxPhase::Suspended as i32); + assert_eq!(driver.stop_calls(), 1); + assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(session.object_id()) + .await + .unwrap() + .is_none(), + "suspension revokes ephemeral SSH sessions" + ); + + let suspended_again = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + assert_eq!(suspended_again.phase(), SandboxPhase::Suspended as i32); + assert_eq!(driver.stop_calls(), 1, "stable suspension is idempotent"); + + let resuming = runtime + .resume_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); + assert_eq!(driver.resume_calls(), 1); + + let resuming_again = runtime + .resume_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + assert_eq!(resuming_again.phase(), SandboxPhase::Resuming as i32); + assert_eq!(driver.resume_calls(), 1, "in-flight resume is idempotent"); + + register_test_supervisor_session(&runtime, sandbox.object_id()); + runtime + .apply_sandbox_update(ready_driver_sandbox( + sandbox.object_id(), + sandbox.object_name(), + )) + .await + .unwrap(); + let ready = runtime + .resume_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + assert_eq!(ready.phase(), SandboxPhase::Ready as i32); + assert_eq!(driver.resume_calls(), 1, "ready resume is idempotent"); + } + + #[tokio::test] + async fn lifecycle_operations_reject_invalid_source_phases() { + let runtime = test_runtime(Arc::new(TestDriver::default())).await; + let sandbox = sandbox_record( + "sb-provisioning", + "sandbox-provisioning", + SandboxPhase::Provisioning, + ); + runtime.store.put_message(&sandbox).await.unwrap(); + + let suspend = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap_err(); + assert_eq!(suspend.code(), Code::FailedPrecondition); + + let resume = runtime + .resume_sandbox("default", sandbox.object_name()) + .await + .unwrap_err(); + assert_eq!(resume.code(), Code::FailedPrecondition); + } + + #[tokio::test] + async fn stale_ready_snapshot_cannot_wake_suspended_sandbox() { + let runtime = test_runtime(Arc::new(TestDriver::default())).await; + let sandbox = sandbox_record("sb-sleeping", "sandbox-sleeping", SandboxPhase::Suspended); + runtime.store.put_message(&sandbox).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + + runtime + .apply_sandbox_update(ready_driver_sandbox( + sandbox.object_id(), + sandbox.object_name(), + )) + .await + .unwrap(); + + let current = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(current.phase(), SandboxPhase::Suspended as i32); + } + + #[tokio::test] + async fn stopped_container_snapshot_confirms_suspending_sandbox() { + let runtime = test_runtime(Arc::new(TestDriver::default())).await; + let sandbox = sandbox_record( + "sb-suspending", + "sandbox-suspending", + SandboxPhase::Suspending, + ); + runtime.store.put_message(&sandbox).await.unwrap(); + let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); + stopped.status = Some(make_driver_status(make_driver_condition( + "ContainerExited", + "container stopped for suspension", + ))); + + runtime.apply_sandbox_update(stopped).await.unwrap(); + + let current = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(current.phase(), SandboxPhase::Suspended as i32); + } + + #[tokio::test] + async fn stopped_container_snapshot_cannot_error_suspended_sandbox() { + let runtime = test_runtime(Arc::new(TestDriver::default())).await; + let sandbox = sandbox_record("sb-suspended", "sandbox-suspended", SandboxPhase::Suspended); + runtime.store.put_message(&sandbox).await.unwrap(); + let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); + stopped.status = Some(make_driver_status(make_driver_condition( + "ContainerExited", + "container is stopped", + ))); + + runtime.apply_sandbox_update(stopped).await.unwrap(); + + let current = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(current.phase(), SandboxPhase::Suspended as i32); + } + #[tokio::test] async fn begin_sandbox_delete_retries_after_stale_snapshot_conflict() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; @@ -4814,7 +5458,7 @@ mod tests { let sandbox = sandbox_record("sb-1", "sandbox-a", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); - let delete_gate = runtime.delete_gates.gate_for(sandbox.object_id()); + let delete_gate = runtime.lifecycle_gates.gate_for(sandbox.object_id()); let first_runtime = runtime.clone(); let first = tokio::spawn(async move { first_runtime.delete_sandbox("default", "sandbox-a").await }); @@ -4878,7 +5522,7 @@ mod tests { let sandbox = sandbox_record("sb-1", "sandbox-a", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); - let delete_gate = runtime.delete_gates.gate_for(sandbox.object_id()); + let delete_gate = runtime.lifecycle_gates.gate_for(sandbox.object_id()); let first_runtime = runtime.clone(); let first = tokio::spawn(async move { first_runtime.delete_sandbox("default", "sandbox-a").await }); @@ -4937,7 +5581,7 @@ mod tests { // Hold the original ID's gate so the request resolves the name and // then waits before it can revalidate the durable row. - let delete_gate = runtime.delete_gates.gate_for(original.object_id()); + let delete_gate = runtime.lifecycle_gates.gate_for(original.object_id()); let delete_guard = delete_gate.lock().await; let delete_runtime = runtime.clone(); let delete = diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index d84ca41557..5339352c57 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -44,9 +44,10 @@ use openshell_core::proto::{ RefreshSandboxTokenRequest, RefreshSandboxTokenResponse, RejectDraftChunkRequest, RejectDraftChunkResponse, RelayFrame, RemoveWorkspaceMemberRequest, RemoveWorkspaceMemberResponse, ReportPolicyStatusRequest, ReportPolicyStatusResponse, - RevokeSshSessionRequest, RevokeSshSessionResponse, RotateProviderCredentialRequest, - RotateProviderCredentialResponse, SandboxResponse, ServiceEndpointResponse, ServiceStatus, - SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, SupervisorMessage, TcpForwardFrame, + ResumeSandboxRequest, RevokeSshSessionRequest, RevokeSshSessionResponse, + RotateProviderCredentialRequest, RotateProviderCredentialResponse, SandboxResponse, + ServiceEndpointResponse, ServiceStatus, SubmitPolicyAnalysisRequest, + SubmitPolicyAnalysisResponse, SupervisorMessage, SuspendSandboxRequest, TcpForwardFrame, UndoDraftChunkRequest, UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, UpdateProviderProfilesRequest, UpdateProviderProfilesResponse, UpdateProviderRequest, WatchSandboxRequest, open_shell_server::OpenShell, @@ -323,6 +324,20 @@ impl OpenShell for OpenShellService { sandbox::handle_delete_sandbox(&self.state, request).await } + async fn suspend_sandbox( + &self, + request: Request, + ) -> Result, Status> { + sandbox::handle_suspend_sandbox(&self.state, request).await + } + + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + sandbox::handle_resume_sandbox(&self.state, request).await + } + // --- Exec --- type ExecSandboxStream = ReceiverStream>; diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index 504532d5eb..d8d707a061 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -22,9 +22,10 @@ use openshell_core::proto::{ DetachSandboxProviderRequest, DetachSandboxProviderResponse, ExecSandboxEvent, ExecSandboxExit, ExecSandboxInput, ExecSandboxRequest, ExecSandboxStderr, ExecSandboxStdout, GetSandboxRequest, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxesRequest, - ListSandboxesResponse, Provider, RevokeSshSessionRequest, RevokeSshSessionResponse, - SandboxResponse, SandboxStreamEvent, SshRelayTarget, TcpForwardFrame, TcpForwardInit, - TcpRelayTarget, WatchSandboxRequest, relay_open, tcp_forward_init, + ListSandboxesResponse, Provider, ResumeSandboxRequest, RevokeSshSessionRequest, + RevokeSshSessionResponse, SandboxResponse, SandboxStreamEvent, SshRelayTarget, + SuspendSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, WatchSandboxRequest, + relay_open, tcp_forward_init, }; use openshell_core::proto::{Sandbox, SandboxPhase, SandboxTemplate, SshSession}; use openshell_core::telemetry::{ @@ -763,6 +764,94 @@ async fn handle_delete_sandbox_inner( })) } +pub(super) async fn handle_suspend_sandbox( + state: &Arc, + request: Request, +) -> Result, Status> { + let result = handle_suspend_sandbox_inner(state, request).await; + openshell_core::telemetry::emit_lifecycle( + LifecycleResource::Sandbox, + LifecycleOperation::Suspend, + if result.is_ok() { + TelemetryOutcome::Success + } else { + TelemetryOutcome::Failure + }, + ); + result +} + +async fn handle_suspend_sandbox_inner( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = super::extract_principal(&request)?; + let req = request.into_inner(); + if req.name.is_empty() { + return Err(Status::invalid_argument("name is required")); + } + let authz = authorize_workspace( + &state.store, + &state.admin_role, + &principal, + &req.workspace, + MinWorkspaceRole::User, + ) + .await?; + let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) + .await? + .name; + let sandbox = state.compute.suspend_sandbox(&workspace, &req.name).await?; + info!(sandbox_name = %req.name, "SuspendSandbox request completed successfully"); + Ok(Response::new(SandboxResponse { + sandbox: Some(sandbox), + })) +} + +pub(super) async fn handle_resume_sandbox( + state: &Arc, + request: Request, +) -> Result, Status> { + let result = handle_resume_sandbox_inner(state, request).await; + openshell_core::telemetry::emit_lifecycle( + LifecycleResource::Sandbox, + LifecycleOperation::Resume, + if result.is_ok() { + TelemetryOutcome::Success + } else { + TelemetryOutcome::Failure + }, + ); + result +} + +async fn handle_resume_sandbox_inner( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = super::extract_principal(&request)?; + let req = request.into_inner(); + if req.name.is_empty() { + return Err(Status::invalid_argument("name is required")); + } + let authz = authorize_workspace( + &state.store, + &state.admin_role, + &principal, + &req.workspace, + MinWorkspaceRole::User, + ) + .await?; + let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) + .await? + .name; + let sandbox = state.compute.resume_sandbox(&workspace, &req.name).await?; + info!(sandbox_name = %req.name, "ResumeSandbox request completed successfully"); + Ok(Response::new(SandboxResponse { + sandbox: Some(sandbox), + })) +} + async fn sandbox_by_name( state: &Arc, workspace: &str, @@ -2725,7 +2814,7 @@ mod tests { .await }); tokio::time::timeout(std::time::Duration::from_secs(5), async { - while state.compute.delete_gate_entry_count() == 0 { + while state.compute.lifecycle_gate_entry_count() == 0 { tokio::time::sleep(std::time::Duration::from_millis(10)).await; } }) @@ -4426,6 +4515,31 @@ mod tests { Code::PermissionDenied, "handle_delete_sandbox should reject non-members with PermissionDenied" ); + + for result in [ + handle_suspend_sandbox( + &state, + non_member_request(SuspendSandboxRequest { + workspace: "no-such-ws".into(), + name: "any".into(), + }), + ) + .await, + handle_resume_sandbox( + &state, + non_member_request(ResumeSandboxRequest { + workspace: "no-such-ws".into(), + name: "any".into(), + }), + ) + .await, + ] { + assert_eq!( + result.unwrap_err().code(), + Code::PermissionDenied, + "lifecycle handlers should reject non-members" + ); + } } /// ID-based data-plane handlers must return `NOT_FOUND` — never diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index e6b8085151..11a1eac540 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -141,6 +141,20 @@ impl SupervisorSessionRegistry { self.sessions.lock().unwrap().remove(sandbox_id); } + /// Disconnect the current supervisor session for a sandbox. + /// + /// Lifecycle suspension uses this to ensure a later resume must establish + /// a fresh session before the sandbox can return to Ready. + pub fn disconnect(&self, sandbox_id: &str) -> bool { + let session = self.sessions.lock().unwrap().remove(sandbox_id); + if let Some(session) = session { + let _ = session.shutdown.send(()); + true + } else { + false + } + } + /// Remove the session only if its `session_id` matches the one we are /// cleaning up. Returns `true` if the entry was removed. /// diff --git a/crates/openshell-server/src/test_support.rs b/crates/openshell-server/src/test_support.rs index d1aa10da11..43c7444178 100644 --- a/crates/openshell-server/src/test_support.rs +++ b/crates/openshell-server/src/test_support.rs @@ -11,9 +11,10 @@ use openshell_core::proto::compute::v1::{ DriverSandbox, GatewayListenerRequirement, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, ListSandboxesRequest, ListSandboxesResponse, - StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, - ValidateSandboxCreateResponse, WatchSandboxesEvent, WatchSandboxesRequest, - compute_driver_server::ComputeDriver, gateway_listener_requirement::Selector, + ResumeSandboxRequest, ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, + ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, + WatchSandboxesRequest, compute_driver_server::ComputeDriver, + gateway_listener_requirement::Selector, }; use std::collections::HashMap; #[cfg(unix)] @@ -51,6 +52,10 @@ pub enum FakeComputeDriverCall { sandbox_id: String, sandbox_name: String, }, + ResumeSandbox { + sandbox_id: String, + sandbox_name: String, + }, DeleteSandbox { sandbox_id: String, sandbox_name: String, @@ -344,6 +349,21 @@ impl ComputeDriver for FakeComputeDriver { Ok(Response::new(StopSandboxResponse {})) } + async fn resume_sandbox( + &self, + request: Request, + ) -> Result, Status> { + self.record_traceparent(request.metadata()); + let request = request.into_inner(); + self.with_state(|state| { + state.calls.push(FakeComputeDriverCall::ResumeSandbox { + sandbox_id: request.sandbox_id, + sandbox_name: request.sandbox_name, + }); + }); + Ok(Response::new(ResumeSandboxResponse {})) + } + async fn delete_sandbox( &self, request: Request, diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index cfd7faa3d6..7bdb7b759c 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -83,6 +83,20 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } + async fn suspend_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn resume_sandbox( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, _request: tonic::Request, diff --git a/crates/openshell-server/tests/supervisor_relay_integration.rs b/crates/openshell-server/tests/supervisor_relay_integration.rs index be1af8f48c..4809433f6b 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -130,6 +130,18 @@ impl OpenShell for RelayGateway { ) -> Result, Status> { Err(Status::unimplemented("unused")) } + async fn suspend_sandbox( + &self, + _: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn resume_sandbox( + &self, + _: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } async fn get_sandbox( &self, _: tonic::Request, diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index ea4c1a37b0..1d51d4691d 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -8,10 +8,16 @@ keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Docker, Podman, position: 4 --- -The gateway's configured compute driver determines how OpenShell creates each sandbox. The CLI workflow stays the same across drivers: you create, connect to, inspect, and delete sandboxes through the gateway API. +The gateway's configured compute driver determines how OpenShell creates each sandbox. The CLI workflow stays the same across drivers: you create, connect to, inspect, suspend, resume, and delete sandboxes through the gateway API. Every compute driver runs the OpenShell supervisor inside the sandbox workload. The supervisor launches the agent process, applies policy, routes egress through the proxy, injects configured credentials, and maintains the gateway session. +Suspend stops compute but retains the sandbox record and the driver's +persistent workspace boundary. Resume reactivates the same driver resource. +Delete remains independent and removes compute plus driver-owned persistent +state. While a sandbox is suspended, gateway access paths and exposed services +remain unavailable. + ## Configure a Compute Driver Configure the compute driver on the gateway. Current releases accept one driver per gateway. Set `compute_drivers` in the gateway TOML file: @@ -129,6 +135,11 @@ For maintainer-level implementation details, refer to the [Docker driver README] Select Docker with `compute_drivers = ["docker"]` in `[openshell.gateway]`. Configure Docker driver values such as `socket_path`, `grpc_endpoint`, `network_name`, `supervisor_bin`, `supervisor_image`, `image_pull_policy`, `ssh_socket_path`, `sandbox_pids_limit`, and `guest_tls_*` in `[openshell.drivers.docker]`. When `socket_path` is unset, the driver uses the same responsive local socket selected by auto-detection. An explicitly selected Docker driver falls back to `/var/run/docker.sock` when no candidate responds. +Suspend stops the existing Docker container without removing its writable +layer or attached volumes. Resume starts that same container. A durably +suspended container stays stopped across gateway restart, and delete remains +responsible for removing it. + For GPU-backed Docker sandboxes, configure Docker CDI before starting the gateway so OpenShell can detect the daemon capability. ### Docker Driver Config Mounts @@ -197,6 +208,10 @@ Select Podman with `compute_drivers = ["podman"]` in `[openshell.gateway]`. Conf Podman sandboxes default to a 45-second graceful stop window before Podman escalates from `SIGTERM` to `SIGKILL`. Set `stop_timeout_secs` in gateway config, or `OPENSHELL_STOP_TIMEOUT` for the standalone driver, when a local runtime needs a different teardown window. +Suspend stops the existing Podman container while retaining its named workspace +volume and driver-owned secrets. Resume starts the same container. Delete is +the operation that removes the container and named volume. + For proxy-required networks, the Podman driver also accepts the corporate egress proxy keys `https_proxy`, `no_proxy`, `proxy_auth_file`, `proxy_auth_allow_insecure`, and `proxy_connect_by_hostname`. The supervisor chains policy-approved TLS tunnels through the proxy with HTTP CONNECT instead of dialing destinations directly. See the [Gateway Configuration File](./gateway-config) reference for the full contract, including the cleartext-credential acknowledgement and the validated-IP CONNECT behavior. On macOS with `podman machine`, the driver uses gvproxy's host-loopback IP, `192.168.127.254`, for sandbox host aliases by default. Set `host_gateway_ip` only when your Podman machine uses a non-standard host-loopback address. On Linux, an empty `host_gateway_ip` keeps Podman's `host-gateway` resolver behavior. Direct local callbacks from rootless Podman require Podman to report the pasta network helper. Slirp4netns, other helpers, and Podman versions that do not report their helper require an explicitly remote `grpc_endpoint`; otherwise the gateway fails startup rather than leaving sandbox callbacks unreachable. Rootful Podman continues to use the configured network's bridge gateway address. @@ -269,6 +284,10 @@ VM sandbox creation follows the same progress model as Kubernetes-backed sandbox On gateway restart, the gateway starts a fresh VM driver process. The driver scans its state directory for accepted sandbox launch records, restarts those VMs, and reuses each sandbox's existing `overlay.ext4` so files written inside the sandbox remain available after the supervisor reconnects. +Suspended VM state directories contain a marker that prevents startup from +launching the VM. The driver retains `sandbox.pb`, `overlay.ext4`, and extension +state, then removes the marker and restores the same overlay on resume. + For maintainer-level implementation details, refer to the [VM driver README](https://github.com/NVIDIA/OpenShell/blob/main/crates/openshell-driver-vm/README.md). ### Enable the VM Driver @@ -358,6 +377,11 @@ process/binary identity through `/proc/`. The Kubernetes driver creates namespaced `agents.x-k8s.io` `Sandbox` resources from the Kubernetes SIG Apps [agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox) project. It detects the served Sandbox API at runtime, caches the selected API version for the gateway process, and uses `v1beta1` when available before falling back to `v1alpha1`, so supported Agent Sandbox installations work without version-specific operator configuration. The Agent Sandbox controller turns those resources into sandbox pods and related storage. +Suspend patches the existing resource rather than deleting it. For `v1beta1`, +the driver sets `spec.operatingMode` to `Suspended` or `Running`. For +`v1alpha1`, it sets `spec.replicas` to `0` or `1`. The Sandbox resource and its +workspace PVC keep their identity across both operations. + If Agent Sandbox is upgraded in place, restart the OpenShell gateway after the controller and CRD rollout completes so the gateway can detect the served API versions again. diff --git a/docs/sandboxes/manage-sandboxes.mdx b/docs/sandboxes/manage-sandboxes.mdx index bc408c4ecd..0218fb5cfb 100644 --- a/docs/sandboxes/manage-sandboxes.mdx +++ b/docs/sandboxes/manage-sandboxes.mdx @@ -436,6 +436,26 @@ back to an unfiltered upload and prints a warning. Pass `--no-git-ignore` to opt into unfiltered uploads explicitly, upload a path outside the Git work tree, or force-add the intended files if they should remain Git-aware. +## Suspend and Resume Sandboxes + +Suspend compute when you want to retain a sandbox and its persistent workspace +without keeping its container, pod, or VM running: + +```shell +openshell sandbox suspend my-sandbox +openshell sandbox resume my-sandbox +``` + +The name is optional and defaults to the last-used sandbox. Suspend stops local +background forwards and waits for the `Suspended` phase. Resume waits until the +same sandbox returns to `Ready`. While suspended, you cannot connect, execute +commands, transfer files, forward ports, or reach exposed services. Policies, +provider attachments, settings, service definitions, and persistent workspace +data remain associated with the sandbox. + +Suspend and resume are idempotent. Delete a suspended sandbox normally when you +no longer need its retained state. + ## Delete Sandboxes Deleting a sandbox stops all processes, releases resources, and purges injected credentials. @@ -452,6 +472,9 @@ Every sandbox moves through a defined set of phases: | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Provisioning | The runtime is setting up the sandbox environment, or the gateway is waiting for the sandbox supervisor to establish its authenticated control session. | | Ready | The sandbox is running and its supervisor control session is connected. You can connect, execute commands, sync files, and view logs. | +| Suspending | The gateway accepted a suspend request and is stopping compute while retaining persistent state. | +| Suspended | Compute is stopped and access is unavailable. The sandbox record and driver-owned persistent workspace remain. | +| Resuming | Compute is restarting. The sandbox becomes usable only after a fresh supervisor session connects. | | Error | Something went wrong during provisioning or execution. Check logs with `openshell logs` for details. | | Deleting | The sandbox is being torn down. The system releases resources and purges credentials. | diff --git a/e2e/rust/tests/sandbox_lifecycle.rs b/e2e/rust/tests/sandbox_lifecycle.rs index 4b832411fe..261c741566 100644 --- a/e2e/rust/tests/sandbox_lifecycle.rs +++ b/e2e/rust/tests/sandbox_lifecycle.rs @@ -8,6 +8,7 @@ use std::time::Duration; use openshell_e2e::harness::binary::{openshell_cmd, openshell_tty_cmd}; use openshell_e2e::harness::output::{extract_field, strip_ansi}; +use openshell_e2e::harness::sandbox::SandboxGuard; use tokio::time::{Instant, sleep}; const SANDBOX_PRESENCE_TIMEOUT: Duration = Duration::from_secs(30); @@ -105,6 +106,86 @@ async fn delete_sandbox(name: &str) { let _ = cmd.status().await; } +async fn run_sandbox_lifecycle_command(operation: &str, name: &str) -> String { + let mut cmd = openshell_cmd(); + cmd.args(["sandbox", operation, name]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let output = cmd + .output() + .await + .unwrap_or_else(|error| panic!("spawn openshell sandbox {operation}: {error}")); + let combined = normalize_output(&format!( + "{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + )); + assert!( + output.status.success(), + "sandbox {operation} should succeed (exit {:?}):\n{combined}", + output.status.code(), + ); + combined +} + +#[tokio::test] +async fn sandbox_suspend_resume_preserves_workspace() { + const SENTINEL: &str = "openshell-suspend-resume-sentinel"; + const SENTINEL_PATH: &str = "/sandbox/.openshell-suspend-resume-e2e"; + let write_sentinel = format!("printf '%s\\n' '{SENTINEL}' > '{SENTINEL_PATH}'"); + + let mut sandbox = SandboxGuard::create(&["--", "sh", "-lc", &write_sentinel]) + .await + .expect("sandbox create should write the workspace sentinel"); + + let suspend_output = run_sandbox_lifecycle_command("suspend", &sandbox.name).await; + assert!( + suspend_output.contains("Suspended sandbox"), + "expected suspend confirmation in:\n{suspend_output}", + ); + + let mut exec_cmd = openshell_cmd(); + exec_cmd + .args([ + "sandbox", + "exec", + "--name", + &sandbox.name, + "--no-tty", + "--", + "cat", + SENTINEL_PATH, + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + let suspended_exec = exec_cmd + .output() + .await + .expect("spawn openshell sandbox exec while suspended"); + assert!( + !suspended_exec.status.success(), + "sandbox exec should fail while suspended" + ); + + let resume_output = run_sandbox_lifecycle_command("resume", &sandbox.name).await; + assert!( + resume_output.contains("Resumed sandbox"), + "expected resume confirmation in:\n{resume_output}", + ); + + let sentinel = sandbox + .exec(&["cat", SENTINEL_PATH]) + .await + .expect("sandbox exec should succeed after resume"); + assert!( + sentinel.lines().any(|line| line.trim() == SENTINEL), + "workspace sentinel should survive suspend and resume:\n{sentinel}", + ); + + sandbox.cleanup().await; +} + #[tokio::test] async fn sandbox_create_keeps_sandbox_after_tty_command_by_default() { let mut cmd = openshell_tty_cmd(&["sandbox", "create", "--", "echo", "OK"]); diff --git a/proto/compute_driver.proto b/proto/compute_driver.proto index e3f18af19f..e916c1d4df 100644 --- a/proto/compute_driver.proto +++ b/proto/compute_driver.proto @@ -40,9 +40,12 @@ service ComputeDriver { // Provision platform resources for a sandbox. rpc CreateSandbox(CreateSandboxRequest) returns (CreateSandboxResponse); - // Stop platform resources for a sandbox without deleting its record. + // Idempotently stop platform resources without deleting persistent state. rpc StopSandbox(StopSandboxRequest) returns (StopSandboxResponse); + // Idempotently resume platform resources for a stopped sandbox. + rpc ResumeSandbox(ResumeSandboxRequest) returns (ResumeSandboxResponse); + // Tear down platform resources for a sandbox. rpc DeleteSandbox(DeleteSandboxRequest) returns (DeleteSandboxResponse); @@ -286,6 +289,15 @@ message StopSandboxRequest { message StopSandboxResponse {} +message ResumeSandboxRequest { + // Stable sandbox ID stored by the gateway. + string sandbox_id = 1; + // Compute-runtime name used by the driver. + string sandbox_name = 2; +} + +message ResumeSandboxResponse {} + message DeleteSandboxRequest { // Stable sandbox ID stored by the gateway. string sandbox_id = 1; diff --git a/proto/openshell.proto b/proto/openshell.proto index 49f6581e7c..b923ec30db 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -108,6 +108,24 @@ service OpenShell { }; } + // Suspend a sandbox while retaining its persistent state. + rpc SuspendSandbox(SuspendSandboxRequest) returns (SandboxResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } + + // Resume a previously suspended sandbox. + rpc ResumeSandbox(ResumeSandboxRequest) returns (SandboxResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } + // Create a short-lived SSH session for a sandbox. rpc CreateSshSession(CreateSshSessionRequest) returns (CreateSshSessionResponse) { option (openshell.options.v1.authorization) = { @@ -879,6 +897,9 @@ enum SandboxPhase { SANDBOX_PHASE_ERROR = 3; SANDBOX_PHASE_DELETING = 4; SANDBOX_PHASE_UNKNOWN = 5; + SANDBOX_PHASE_SUSPENDING = 6; + SANDBOX_PHASE_SUSPENDED = 7; + SANDBOX_PHASE_RESUMING = 8; } // Public platform event exposed on the sandbox watch stream. @@ -976,6 +997,22 @@ message DeleteSandboxRequest { string workspace = 2; } +// Suspend sandbox request. +message SuspendSandboxRequest { + // Sandbox name (canonical lookup key). + string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; +} + +// Resume sandbox request. +message ResumeSandboxRequest { + // Sandbox name (canonical lookup key). + string name = 1; + // Workspace scope. Empty defaults to "default". + string workspace = 2; +} + // Sandbox response. message SandboxResponse { Sandbox sandbox = 1; diff --git a/python/openshell/sandbox.py b/python/openshell/sandbox.py index b62a9c8855..d976014b99 100644 --- a/python/openshell/sandbox.py +++ b/python/openshell/sandbox.py @@ -250,6 +250,16 @@ def exec_python( def delete(self) -> bool: return self._client.delete(self.sandbox.name, workspace=self._workspace) + def suspend(self) -> SandboxRef: + self.sandbox = self._client.suspend( + self.sandbox.name, workspace=self._workspace + ) + return self.sandbox + + def resume(self) -> SandboxRef: + self.sandbox = self._client.resume(self.sandbox.name, workspace=self._workspace) + return self.sandbox + class SandboxClient: """gRPC client for sandbox CRUD and command execution.""" @@ -546,6 +556,20 @@ def delete(self, sandbox_name: str, *, workspace: str) -> bool: ) return bool(response.deleted) + def suspend(self, sandbox_name: str, *, workspace: str) -> SandboxRef: + response = self._stub.SuspendSandbox( + openshell_pb2.SuspendSandboxRequest(name=sandbox_name, workspace=workspace), + timeout=self._timeout, + ) + return _sandbox_ref(response.sandbox) + + def resume(self, sandbox_name: str, *, workspace: str) -> SandboxRef: + response = self._stub.ResumeSandbox( + openshell_pb2.ResumeSandboxRequest(name=sandbox_name, workspace=workspace), + timeout=self._timeout, + ) + return _sandbox_ref(response.sandbox) + def wait_deleted( self, sandbox_name: str, *, workspace: str, timeout_seconds: float = 60.0 ) -> None: @@ -565,16 +589,46 @@ def wait_deleted( def wait_ready( self, sandbox_name: str, *, workspace: str, timeout_seconds: float = 300.0 + ) -> SandboxRef: + return self._wait_for_phase( + sandbox_name, + workspace=workspace, + target_phase=openshell_pb2.SANDBOX_PHASE_READY, + target_name="ready", + timeout_seconds=timeout_seconds, + ) + + def wait_suspended( + self, sandbox_name: str, *, workspace: str, timeout_seconds: float = 300.0 + ) -> SandboxRef: + return self._wait_for_phase( + sandbox_name, + workspace=workspace, + target_phase=openshell_pb2.SANDBOX_PHASE_SUSPENDED, + target_name="suspended", + timeout_seconds=timeout_seconds, + ) + + def _wait_for_phase( + self, + sandbox_name: str, + *, + workspace: str, + target_phase: int, + target_name: str, + timeout_seconds: float, ) -> SandboxRef: deadline = time.time() + timeout_seconds while time.time() < deadline: sandbox = self.get(sandbox_name, workspace=workspace) - if sandbox.status.phase == openshell_pb2.SANDBOX_PHASE_READY: + if sandbox.status.phase == target_phase: return sandbox if sandbox.status.phase == openshell_pb2.SANDBOX_PHASE_ERROR: raise SandboxError(f"sandbox {sandbox_name} entered error phase") time.sleep(1) - raise SandboxError(f"sandbox {sandbox_name} was not ready within timeout") + raise SandboxError( + f"sandbox {sandbox_name} was not {target_name} within timeout" + ) def exec_stream( self, diff --git a/python/openshell/sandbox_test.py b/python/openshell/sandbox_test.py index f1cb06148c..51a6bcb1c5 100644 --- a/python/openshell/sandbox_test.py +++ b/python/openshell/sandbox_test.py @@ -1482,6 +1482,8 @@ def __init__(self, listed: list[openshell_pb2.Sandbox] | None = None) -> None: self.list_request: openshell_pb2.ListSandboxesRequest | None = None self.get_request: openshell_pb2.GetSandboxRequest | None = None self.delete_request: openshell_pb2.DeleteSandboxRequest | None = None + self.suspend_request: openshell_pb2.SuspendSandboxRequest | None = None + self.resume_request: openshell_pb2.ResumeSandboxRequest | None = None self._listed = listed or [] def GetSandbox( @@ -1506,6 +1508,38 @@ def DeleteSandbox( _ = timeout return SimpleNamespace(deleted=True) + def SuspendSandbox( + self, + request: openshell_pb2.SuspendSandboxRequest, + timeout: float | None = None, + ) -> Any: + self.suspend_request = request + _ = timeout + return SimpleNamespace( + sandbox=_make_sandbox_proto( + "sandbox-1", + request.name, + phase=openshell_pb2.SANDBOX_PHASE_SUSPENDED, + workspace=request.workspace, + ) + ) + + def ResumeSandbox( + self, + request: openshell_pb2.ResumeSandboxRequest, + timeout: float | None = None, + ) -> Any: + self.resume_request = request + _ = timeout + return SimpleNamespace( + sandbox=_make_sandbox_proto( + "sandbox-1", + request.name, + phase=openshell_pb2.SANDBOX_PHASE_RESUMING, + workspace=request.workspace, + ) + ) + def CreateSandbox( self, request: openshell_pb2.CreateSandboxRequest, @@ -1580,6 +1614,23 @@ def test_create_forwards_name_and_labels() -> None: assert dict(ref.labels) == {"aiq": "deep-research"} +def test_suspend_and_resume_forward_workspace_and_return_phase() -> None: + stub = _FakeSandboxStub() + client = _client_with_fake_stub(stub) + + suspended = client.suspend("job-1", workspace="team-a") + assert stub.suspend_request is not None + assert stub.suspend_request.name == "job-1" + assert stub.suspend_request.workspace == "team-a" + assert suspended.phase == openshell_pb2.SANDBOX_PHASE_SUSPENDED + + resuming = client.resume("job-1", workspace="team-a") + assert stub.resume_request is not None + assert stub.resume_request.name == "job-1" + assert stub.resume_request.workspace == "team-a" + assert resuming.phase == openshell_pb2.SANDBOX_PHASE_RESUMING + + def test_create_without_args_sends_empty_metadata() -> None: stub = _FakeSandboxStub() client = _client_with_fake_stub(stub) diff --git a/sdk/go/openshell/v1/internal/converter/sandbox.go b/sdk/go/openshell/v1/internal/converter/sandbox.go index b522454b83..8838480ed9 100644 --- a/sdk/go/openshell/v1/internal/converter/sandbox.go +++ b/sdk/go/openshell/v1/internal/converter/sandbox.go @@ -110,6 +110,12 @@ func SandboxPhaseFromProto(phase pb.SandboxPhase) types.SandboxPhase { return types.SandboxDeleting case pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN: return types.SandboxUnknown + case pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING: + return types.SandboxSuspending + case pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED: + return types.SandboxSuspended + case pb.SandboxPhase_SANDBOX_PHASE_RESUMING: + return types.SandboxResuming default: return types.SandboxUnknown } @@ -128,6 +134,12 @@ func SandboxPhaseToProto(phase types.SandboxPhase) pb.SandboxPhase { return pb.SandboxPhase_SANDBOX_PHASE_DELETING case types.SandboxUnknown: return pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN + case types.SandboxSuspending: + return pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING + case types.SandboxSuspended: + return pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED + case types.SandboxResuming: + return pb.SandboxPhase_SANDBOX_PHASE_RESUMING default: return pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN } diff --git a/sdk/go/openshell/v1/internal/converter/sandbox_test.go b/sdk/go/openshell/v1/internal/converter/sandbox_test.go index 8cbc4d3b81..ec8c0d833a 100644 --- a/sdk/go/openshell/v1/internal/converter/sandbox_test.go +++ b/sdk/go/openshell/v1/internal/converter/sandbox_test.go @@ -142,6 +142,9 @@ func TestSandboxPhaseFromProto(t *testing.T) { {pb.SandboxPhase_SANDBOX_PHASE_ERROR, v1.SandboxError}, {pb.SandboxPhase_SANDBOX_PHASE_DELETING, v1.SandboxDeleting}, {pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN, v1.SandboxUnknown}, + {pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING, v1.SandboxSuspending}, + {pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED, v1.SandboxSuspended}, + {pb.SandboxPhase_SANDBOX_PHASE_RESUMING, v1.SandboxResuming}, {pb.SandboxPhase_SANDBOX_PHASE_UNSPECIFIED, v1.SandboxUnknown}, {pb.SandboxPhase(999), v1.SandboxUnknown}, } @@ -161,6 +164,9 @@ func TestSandboxPhaseToProto(t *testing.T) { {v1.SandboxError, pb.SandboxPhase_SANDBOX_PHASE_ERROR}, {v1.SandboxDeleting, pb.SandboxPhase_SANDBOX_PHASE_DELETING}, {v1.SandboxUnknown, pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN}, + {v1.SandboxSuspending, pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING}, + {v1.SandboxSuspended, pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED}, + {v1.SandboxResuming, pb.SandboxPhase_SANDBOX_PHASE_RESUMING}, {v1.SandboxPhase("bogus"), pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN}, } diff --git a/sdk/go/openshell/v1/sandbox.go b/sdk/go/openshell/v1/sandbox.go index 2dfc6ba8ac..9de8959907 100644 --- a/sdk/go/openshell/v1/sandbox.go +++ b/sdk/go/openshell/v1/sandbox.go @@ -56,11 +56,14 @@ type SandboxInterface interface { Create(ctx context.Context, workspace, name string, spec *SandboxSpec, labels map[string]string) (*Sandbox, error) Get(ctx context.Context, workspace, name string) (*Sandbox, error) List(ctx context.Context, workspace string, opts ...ListOptions) ([]*Sandbox, error) + Suspend(ctx context.Context, workspace, name string) (*Sandbox, error) + Resume(ctx context.Context, workspace, name string) (*Sandbox, error) Delete(ctx context.Context, workspace, name string) error AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) WaitReady(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) + WaitSuspended(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) Watch(ctx context.Context, workspace, name string, opts ...WatchOptions) (WatchInterface[*Sandbox], error) // GetLogs retrieves log entries for a sandbox. The sandbox is resolved // by name (an internal Get call translates name to ID). Use diff --git a/sdk/go/openshell/v1/sandbox_client.go b/sdk/go/openshell/v1/sandbox_client.go index 6c38db7811..103bb764ef 100644 --- a/sdk/go/openshell/v1/sandbox_client.go +++ b/sdk/go/openshell/v1/sandbox_client.go @@ -91,6 +91,28 @@ func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) erro return nil } +func (s *sandboxClient) Suspend(ctx context.Context, workspace, name string) (*Sandbox, error) { + resp, err := s.client.SuspendSandbox(ctx, &pb.SuspendSandboxRequest{ + Name: name, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return converter.SandboxFromProto(resp.GetSandbox()), nil +} + +func (s *sandboxClient) Resume(ctx context.Context, workspace, name string) (*Sandbox, error) { + resp, err := s.client.ResumeSandbox(ctx, &pb.ResumeSandboxRequest{ + Name: name, + Workspace: workspace, + }) + if err != nil { + return nil, converter.FromGRPCError(err) + } + return converter.SandboxFromProto(resp.GetSandbox()), nil +} + func (s *sandboxClient) AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) { resp, err := s.client.AttachSandboxProvider(ctx, &pb.AttachSandboxProviderRequest{ SandboxName: sandboxName, @@ -140,6 +162,14 @@ func (s *sandboxClient) ListProviders(ctx context.Context, workspace, sandboxNam } func (s *sandboxClient) WaitReady(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) { + return s.waitForPhase(ctx, workspace, name, SandboxReady, opts...) +} + +func (s *sandboxClient) WaitSuspended(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) { + return s.waitForPhase(ctx, workspace, name, SandboxSuspended, opts...) +} + +func (s *sandboxClient) waitForPhase(ctx context.Context, workspace, name string, target SandboxPhase, opts ...WaitOptions) (*Sandbox, error) { interval := defaultPollInterval if len(opts) > 0 && opts[0].PollInterval > 0 { interval = opts[0].PollInterval @@ -150,7 +180,7 @@ func (s *sandboxClient) WaitReady(ctx context.Context, workspace, name string, o return nil, err } - if sb.Status.Phase == SandboxReady { + if sb.Status.Phase == target { return sb, nil } if sb.Status.Phase == SandboxError { @@ -172,7 +202,7 @@ func (s *sandboxClient) WaitReady(ctx context.Context, workspace, name string, o if err != nil { return nil, err } - if sb.Status.Phase == SandboxReady { + if sb.Status.Phase == target { return sb, nil } if sb.Status.Phase == SandboxError { diff --git a/sdk/go/openshell/v1/sandbox_client_test.go b/sdk/go/openshell/v1/sandbox_client_test.go index 2574348ec4..e2a8afa89d 100644 --- a/sdk/go/openshell/v1/sandbox_client_test.go +++ b/sdk/go/openshell/v1/sandbox_client_test.go @@ -125,6 +125,28 @@ func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandb return &pb.DeleteSandboxResponse{Deleted: true}, nil } +func (s *mockSandboxServer) SuspendSandbox(_ context.Context, req *pb.SuspendSandboxRequest) (*pb.SandboxResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + sb, ok := s.sandboxes[req.GetName()] + if !ok { + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + } + sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED + return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil +} + +func (s *mockSandboxServer) ResumeSandbox(_ context.Context, req *pb.ResumeSandboxRequest) (*pb.SandboxResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + sb, ok := s.sandboxes[req.GetName()] + if !ok { + return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) + } + sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_RESUMING + return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil +} + func (s *mockSandboxServer) AttachSandboxProvider(_ context.Context, req *pb.AttachSandboxProviderRequest) (*pb.AttachSandboxProviderResponse, error) { s.mu.Lock() defer s.mu.Unlock() @@ -358,6 +380,24 @@ func TestSandboxDelete_NotFound(t *testing.T) { assert.True(t, IsNotFound(err)) } +func TestSandboxSuspendAndResume(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["lifecycle"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "lifecycle", Workspace: "team-a"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_READY}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + suspended, err := client.Suspend(context.Background(), "team-a", "lifecycle") + require.NoError(t, err) + assert.Equal(t, SandboxSuspended, suspended.Status.Phase) + + resuming, err := client.Resume(context.Background(), "team-a", "lifecycle") + require.NoError(t, err) + assert.Equal(t, SandboxResuming, resuming.Status.Phase) +} + // --- T030: AttachProvider, DetachProvider, ListProviders tests --- func TestSandboxAttachProvider(t *testing.T) { @@ -472,6 +512,20 @@ func TestSandboxListProviders_Error(t *testing.T) { // --- T031: WaitReady tests --- +func TestSandboxWaitSuspended_AlreadySuspended(t *testing.T) { + mock := newMockSandboxServer() + mock.sandboxes["sleeping"] = &pb.Sandbox{ + Metadata: &dm.ObjectMeta{Name: "sleeping"}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED}, + } + client, cleanup := setupSandboxTest(t, mock) + defer cleanup() + + result, err := client.WaitSuspended(context.Background(), "default", "sleeping") + require.NoError(t, err) + assert.Equal(t, SandboxSuspended, result.Status.Phase) +} + func TestSandboxWaitReady_AlreadyReady(t *testing.T) { mock := newMockSandboxServer() mock.sandboxes["ready-sb"] = &pb.Sandbox{ diff --git a/sdk/go/openshell/v1/types.go b/sdk/go/openshell/v1/types.go index 012811cabb..955b1d4cfc 100644 --- a/sdk/go/openshell/v1/types.go +++ b/sdk/go/openshell/v1/types.go @@ -17,6 +17,9 @@ const ( SandboxError = types.SandboxError SandboxDeleting = types.SandboxDeleting SandboxUnknown = types.SandboxUnknown + SandboxSuspending = types.SandboxSuspending + SandboxSuspended = types.SandboxSuspended + SandboxResuming = types.SandboxResuming ) // EventType classifies watch events. diff --git a/sdk/go/openshell/v1/types/types.go b/sdk/go/openshell/v1/types/types.go index 01da4ba9ca..7e6190c060 100644 --- a/sdk/go/openshell/v1/types/types.go +++ b/sdk/go/openshell/v1/types/types.go @@ -15,6 +15,9 @@ const ( SandboxError SandboxPhase = "Error" SandboxDeleting SandboxPhase = "Deleting" SandboxUnknown SandboxPhase = "Unknown" + SandboxSuspending SandboxPhase = "Suspending" + SandboxSuspended SandboxPhase = "Suspended" + SandboxResuming SandboxPhase = "Resuming" ) // EventType classifies watch events. diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index a854c751a2..eb64d75103 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -41,6 +41,9 @@ const ( SandboxPhase_SANDBOX_PHASE_ERROR SandboxPhase = 3 SandboxPhase_SANDBOX_PHASE_DELETING SandboxPhase = 4 SandboxPhase_SANDBOX_PHASE_UNKNOWN SandboxPhase = 5 + SandboxPhase_SANDBOX_PHASE_SUSPENDING SandboxPhase = 6 + SandboxPhase_SANDBOX_PHASE_SUSPENDED SandboxPhase = 7 + SandboxPhase_SANDBOX_PHASE_RESUMING SandboxPhase = 8 ) // Enum value maps for SandboxPhase. @@ -52,6 +55,9 @@ var ( 3: "SANDBOX_PHASE_ERROR", 4: "SANDBOX_PHASE_DELETING", 5: "SANDBOX_PHASE_UNKNOWN", + 6: "SANDBOX_PHASE_SUSPENDING", + 7: "SANDBOX_PHASE_SUSPENDED", + 8: "SANDBOX_PHASE_RESUMING", } SandboxPhase_value = map[string]int32{ "SANDBOX_PHASE_UNSPECIFIED": 0, @@ -60,6 +66,9 @@ var ( "SANDBOX_PHASE_ERROR": 3, "SANDBOX_PHASE_DELETING": 4, "SANDBOX_PHASE_UNKNOWN": 5, + "SANDBOX_PHASE_SUSPENDING": 6, + "SANDBOX_PHASE_SUSPENDED": 7, + "SANDBOX_PHASE_RESUMING": 8, } ) @@ -2128,6 +2137,116 @@ func (x *DeleteSandboxRequest) GetWorkspace() string { return "" } +// Suspend sandbox request. +type SuspendSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SuspendSandboxRequest) Reset() { + *x = SuspendSandboxRequest{} + mi := &file_openshell_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SuspendSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SuspendSandboxRequest) ProtoMessage() {} + +func (x *SuspendSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SuspendSandboxRequest.ProtoReflect.Descriptor instead. +func (*SuspendSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{27} +} + +func (x *SuspendSandboxRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SuspendSandboxRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Resume sandbox request. +type ResumeSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResumeSandboxRequest) Reset() { + *x = ResumeSandboxRequest{} + mi := &file_openshell_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResumeSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeSandboxRequest) ProtoMessage() {} + +func (x *ResumeSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeSandboxRequest.ProtoReflect.Descriptor instead. +func (*ResumeSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{28} +} + +func (x *ResumeSandboxRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ResumeSandboxRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + // Sandbox response. type SandboxResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2138,7 +2257,7 @@ type SandboxResponse struct { func (x *SandboxResponse) Reset() { *x = SandboxResponse{} - mi := &file_openshell_proto_msgTypes[27] + mi := &file_openshell_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2150,7 +2269,7 @@ func (x *SandboxResponse) String() string { func (*SandboxResponse) ProtoMessage() {} func (x *SandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[27] + mi := &file_openshell_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2163,7 +2282,7 @@ func (x *SandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. func (*SandboxResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{27} + return file_openshell_proto_rawDescGZIP(), []int{29} } func (x *SandboxResponse) GetSandbox() *Sandbox { @@ -2183,7 +2302,7 @@ type ListSandboxesResponse struct { func (x *ListSandboxesResponse) Reset() { *x = ListSandboxesResponse{} - mi := &file_openshell_proto_msgTypes[28] + mi := &file_openshell_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2195,7 +2314,7 @@ func (x *ListSandboxesResponse) String() string { func (*ListSandboxesResponse) ProtoMessage() {} func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[28] + mi := &file_openshell_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2208,7 +2327,7 @@ func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{28} + return file_openshell_proto_rawDescGZIP(), []int{30} } func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { @@ -2228,7 +2347,7 @@ type ListSandboxProvidersResponse struct { func (x *ListSandboxProvidersResponse) Reset() { *x = ListSandboxProvidersResponse{} - mi := &file_openshell_proto_msgTypes[29] + mi := &file_openshell_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2240,7 +2359,7 @@ func (x *ListSandboxProvidersResponse) String() string { func (*ListSandboxProvidersResponse) ProtoMessage() {} func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[29] + mi := &file_openshell_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2253,7 +2372,7 @@ func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{29} + return file_openshell_proto_rawDescGZIP(), []int{31} } func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { @@ -2275,7 +2394,7 @@ type AttachSandboxProviderResponse struct { func (x *AttachSandboxProviderResponse) Reset() { *x = AttachSandboxProviderResponse{} - mi := &file_openshell_proto_msgTypes[30] + mi := &file_openshell_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2287,7 +2406,7 @@ func (x *AttachSandboxProviderResponse) String() string { func (*AttachSandboxProviderResponse) ProtoMessage() {} func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[30] + mi := &file_openshell_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2300,7 +2419,7 @@ func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{30} + return file_openshell_proto_rawDescGZIP(), []int{32} } func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { @@ -2329,7 +2448,7 @@ type DetachSandboxProviderResponse struct { func (x *DetachSandboxProviderResponse) Reset() { *x = DetachSandboxProviderResponse{} - mi := &file_openshell_proto_msgTypes[31] + mi := &file_openshell_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2341,7 +2460,7 @@ func (x *DetachSandboxProviderResponse) String() string { func (*DetachSandboxProviderResponse) ProtoMessage() {} func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[31] + mi := &file_openshell_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2354,7 +2473,7 @@ func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{31} + return file_openshell_proto_rawDescGZIP(), []int{33} } func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { @@ -2381,7 +2500,7 @@ type DeleteSandboxResponse struct { func (x *DeleteSandboxResponse) Reset() { *x = DeleteSandboxResponse{} - mi := &file_openshell_proto_msgTypes[32] + mi := &file_openshell_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2393,7 +2512,7 @@ func (x *DeleteSandboxResponse) String() string { func (*DeleteSandboxResponse) ProtoMessage() {} func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[32] + mi := &file_openshell_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2406,7 +2525,7 @@ func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{32} + return file_openshell_proto_rawDescGZIP(), []int{34} } func (x *DeleteSandboxResponse) GetDeleted() bool { @@ -2427,7 +2546,7 @@ type CreateSshSessionRequest struct { func (x *CreateSshSessionRequest) Reset() { *x = CreateSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[33] + mi := &file_openshell_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2439,7 +2558,7 @@ func (x *CreateSshSessionRequest) String() string { func (*CreateSshSessionRequest) ProtoMessage() {} func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[33] + mi := &file_openshell_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2452,7 +2571,7 @@ func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{33} + return file_openshell_proto_rawDescGZIP(), []int{35} } func (x *CreateSshSessionRequest) GetSandboxId() string { @@ -2495,7 +2614,7 @@ type CreateSshSessionResponse struct { func (x *CreateSshSessionResponse) Reset() { *x = CreateSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[34] + mi := &file_openshell_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2507,7 +2626,7 @@ func (x *CreateSshSessionResponse) String() string { func (*CreateSshSessionResponse) ProtoMessage() {} func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[34] + mi := &file_openshell_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2520,7 +2639,7 @@ func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{34} + return file_openshell_proto_rawDescGZIP(), []int{36} } func (x *CreateSshSessionResponse) GetSandboxId() string { @@ -2591,7 +2710,7 @@ type ExposeServiceRequest struct { func (x *ExposeServiceRequest) Reset() { *x = ExposeServiceRequest{} - mi := &file_openshell_proto_msgTypes[35] + mi := &file_openshell_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2603,7 +2722,7 @@ func (x *ExposeServiceRequest) String() string { func (*ExposeServiceRequest) ProtoMessage() {} func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[35] + mi := &file_openshell_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2616,7 +2735,7 @@ func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{35} + return file_openshell_proto_rawDescGZIP(), []int{37} } func (x *ExposeServiceRequest) GetSandbox() string { @@ -2669,7 +2788,7 @@ type GetServiceRequest struct { func (x *GetServiceRequest) Reset() { *x = GetServiceRequest{} - mi := &file_openshell_proto_msgTypes[36] + mi := &file_openshell_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2681,7 +2800,7 @@ func (x *GetServiceRequest) String() string { func (*GetServiceRequest) ProtoMessage() {} func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[36] + mi := &file_openshell_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2694,7 +2813,7 @@ func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. func (*GetServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{36} + return file_openshell_proto_rawDescGZIP(), []int{38} } func (x *GetServiceRequest) GetSandbox() string { @@ -2737,7 +2856,7 @@ type ListServicesRequest struct { func (x *ListServicesRequest) Reset() { *x = ListServicesRequest{} - mi := &file_openshell_proto_msgTypes[37] + mi := &file_openshell_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2749,7 +2868,7 @@ func (x *ListServicesRequest) String() string { func (*ListServicesRequest) ProtoMessage() {} func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[37] + mi := &file_openshell_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2762,7 +2881,7 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. func (*ListServicesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{37} + return file_openshell_proto_rawDescGZIP(), []int{39} } func (x *ListServicesRequest) GetSandbox() string { @@ -2810,7 +2929,7 @@ type ListServicesResponse struct { func (x *ListServicesResponse) Reset() { *x = ListServicesResponse{} - mi := &file_openshell_proto_msgTypes[38] + mi := &file_openshell_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2822,7 +2941,7 @@ func (x *ListServicesResponse) String() string { func (*ListServicesResponse) ProtoMessage() {} func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[38] + mi := &file_openshell_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2835,7 +2954,7 @@ func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. func (*ListServicesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{38} + return file_openshell_proto_rawDescGZIP(), []int{40} } func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { @@ -2860,7 +2979,7 @@ type DeleteServiceRequest struct { func (x *DeleteServiceRequest) Reset() { *x = DeleteServiceRequest{} - mi := &file_openshell_proto_msgTypes[39] + mi := &file_openshell_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2872,7 +2991,7 @@ func (x *DeleteServiceRequest) String() string { func (*DeleteServiceRequest) ProtoMessage() {} func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[39] + mi := &file_openshell_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2885,7 +3004,7 @@ func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{39} + return file_openshell_proto_rawDescGZIP(), []int{41} } func (x *DeleteServiceRequest) GetSandbox() string { @@ -2920,7 +3039,7 @@ type DeleteServiceResponse struct { func (x *DeleteServiceResponse) Reset() { *x = DeleteServiceResponse{} - mi := &file_openshell_proto_msgTypes[40] + mi := &file_openshell_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2932,7 +3051,7 @@ func (x *DeleteServiceResponse) String() string { func (*DeleteServiceResponse) ProtoMessage() {} func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[40] + mi := &file_openshell_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2945,7 +3064,7 @@ func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{40} + return file_openshell_proto_rawDescGZIP(), []int{42} } func (x *DeleteServiceResponse) GetDeleted() bool { @@ -2976,7 +3095,7 @@ type ServiceEndpoint struct { func (x *ServiceEndpoint) Reset() { *x = ServiceEndpoint{} - mi := &file_openshell_proto_msgTypes[41] + mi := &file_openshell_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2988,7 +3107,7 @@ func (x *ServiceEndpoint) String() string { func (*ServiceEndpoint) ProtoMessage() {} func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[41] + mi := &file_openshell_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3001,7 +3120,7 @@ func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. func (*ServiceEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{41} + return file_openshell_proto_rawDescGZIP(), []int{43} } func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { @@ -3057,7 +3176,7 @@ type ServiceEndpointResponse struct { func (x *ServiceEndpointResponse) Reset() { *x = ServiceEndpointResponse{} - mi := &file_openshell_proto_msgTypes[42] + mi := &file_openshell_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3069,7 +3188,7 @@ func (x *ServiceEndpointResponse) String() string { func (*ServiceEndpointResponse) ProtoMessage() {} func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[42] + mi := &file_openshell_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3082,7 +3201,7 @@ func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{42} + return file_openshell_proto_rawDescGZIP(), []int{44} } func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { @@ -3110,7 +3229,7 @@ type RevokeSshSessionRequest struct { func (x *RevokeSshSessionRequest) Reset() { *x = RevokeSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[43] + mi := &file_openshell_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3122,7 +3241,7 @@ func (x *RevokeSshSessionRequest) String() string { func (*RevokeSshSessionRequest) ProtoMessage() {} func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[43] + mi := &file_openshell_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3135,7 +3254,7 @@ func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{43} + return file_openshell_proto_rawDescGZIP(), []int{45} } func (x *RevokeSshSessionRequest) GetToken() string { @@ -3156,7 +3275,7 @@ type RevokeSshSessionResponse struct { func (x *RevokeSshSessionResponse) Reset() { *x = RevokeSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[44] + mi := &file_openshell_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3168,7 +3287,7 @@ func (x *RevokeSshSessionResponse) String() string { func (*RevokeSshSessionResponse) ProtoMessage() {} func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[44] + mi := &file_openshell_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3181,7 +3300,7 @@ func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{44} + return file_openshell_proto_rawDescGZIP(), []int{46} } func (x *RevokeSshSessionResponse) GetRevoked() bool { @@ -3218,7 +3337,7 @@ type ExecSandboxRequest struct { func (x *ExecSandboxRequest) Reset() { *x = ExecSandboxRequest{} - mi := &file_openshell_proto_msgTypes[45] + mi := &file_openshell_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3230,7 +3349,7 @@ func (x *ExecSandboxRequest) String() string { func (*ExecSandboxRequest) ProtoMessage() {} func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[45] + mi := &file_openshell_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3243,7 +3362,7 @@ func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxRequest.ProtoReflect.Descriptor instead. func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{45} + return file_openshell_proto_rawDescGZIP(), []int{47} } func (x *ExecSandboxRequest) GetSandboxId() string { @@ -3319,7 +3438,7 @@ type ExecSandboxStdout struct { func (x *ExecSandboxStdout) Reset() { *x = ExecSandboxStdout{} - mi := &file_openshell_proto_msgTypes[46] + mi := &file_openshell_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3331,7 +3450,7 @@ func (x *ExecSandboxStdout) String() string { func (*ExecSandboxStdout) ProtoMessage() {} func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[46] + mi := &file_openshell_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3344,7 +3463,7 @@ func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxStdout.ProtoReflect.Descriptor instead. func (*ExecSandboxStdout) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{46} + return file_openshell_proto_rawDescGZIP(), []int{48} } func (x *ExecSandboxStdout) GetData() []byte { @@ -3364,7 +3483,7 @@ type ExecSandboxStderr struct { func (x *ExecSandboxStderr) Reset() { *x = ExecSandboxStderr{} - mi := &file_openshell_proto_msgTypes[47] + mi := &file_openshell_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3376,7 +3495,7 @@ func (x *ExecSandboxStderr) String() string { func (*ExecSandboxStderr) ProtoMessage() {} func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[47] + mi := &file_openshell_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3389,7 +3508,7 @@ func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxStderr.ProtoReflect.Descriptor instead. func (*ExecSandboxStderr) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{47} + return file_openshell_proto_rawDescGZIP(), []int{49} } func (x *ExecSandboxStderr) GetData() []byte { @@ -3409,7 +3528,7 @@ type ExecSandboxExit struct { func (x *ExecSandboxExit) Reset() { *x = ExecSandboxExit{} - mi := &file_openshell_proto_msgTypes[48] + mi := &file_openshell_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3421,7 +3540,7 @@ func (x *ExecSandboxExit) String() string { func (*ExecSandboxExit) ProtoMessage() {} func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[48] + mi := &file_openshell_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3434,7 +3553,7 @@ func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxExit.ProtoReflect.Descriptor instead. func (*ExecSandboxExit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{48} + return file_openshell_proto_rawDescGZIP(), []int{50} } func (x *ExecSandboxExit) GetExitCode() int32 { @@ -3459,7 +3578,7 @@ type ExecSandboxEvent struct { func (x *ExecSandboxEvent) Reset() { *x = ExecSandboxEvent{} - mi := &file_openshell_proto_msgTypes[49] + mi := &file_openshell_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3471,7 +3590,7 @@ func (x *ExecSandboxEvent) String() string { func (*ExecSandboxEvent) ProtoMessage() {} func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[49] + mi := &file_openshell_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3484,7 +3603,7 @@ func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxEvent.ProtoReflect.Descriptor instead. func (*ExecSandboxEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{49} + return file_openshell_proto_rawDescGZIP(), []int{51} } func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { @@ -3566,7 +3685,7 @@ type TcpForwardInit struct { func (x *TcpForwardInit) Reset() { *x = TcpForwardInit{} - mi := &file_openshell_proto_msgTypes[50] + mi := &file_openshell_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3578,7 +3697,7 @@ func (x *TcpForwardInit) String() string { func (*TcpForwardInit) ProtoMessage() {} func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[50] + mi := &file_openshell_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3591,7 +3710,7 @@ func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpForwardInit.ProtoReflect.Descriptor instead. func (*TcpForwardInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{50} + return file_openshell_proto_rawDescGZIP(), []int{52} } func (x *TcpForwardInit) GetSandboxId() string { @@ -3670,7 +3789,7 @@ type TcpForwardFrame struct { func (x *TcpForwardFrame) Reset() { *x = TcpForwardFrame{} - mi := &file_openshell_proto_msgTypes[51] + mi := &file_openshell_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3682,7 +3801,7 @@ func (x *TcpForwardFrame) String() string { func (*TcpForwardFrame) ProtoMessage() {} func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[51] + mi := &file_openshell_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3695,7 +3814,7 @@ func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpForwardFrame.ProtoReflect.Descriptor instead. func (*TcpForwardFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{51} + return file_openshell_proto_rawDescGZIP(), []int{53} } func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { @@ -3754,7 +3873,7 @@ type ExecSandboxInput struct { func (x *ExecSandboxInput) Reset() { *x = ExecSandboxInput{} - mi := &file_openshell_proto_msgTypes[52] + mi := &file_openshell_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3766,7 +3885,7 @@ func (x *ExecSandboxInput) String() string { func (*ExecSandboxInput) ProtoMessage() {} func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[52] + mi := &file_openshell_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3779,7 +3898,7 @@ func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxInput.ProtoReflect.Descriptor instead. func (*ExecSandboxInput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{52} + return file_openshell_proto_rawDescGZIP(), []int{54} } func (x *ExecSandboxInput) GetPayload() isExecSandboxInput_Payload { @@ -3852,7 +3971,7 @@ type ExecSandboxWindowResize struct { func (x *ExecSandboxWindowResize) Reset() { *x = ExecSandboxWindowResize{} - mi := &file_openshell_proto_msgTypes[53] + mi := &file_openshell_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3864,7 +3983,7 @@ func (x *ExecSandboxWindowResize) String() string { func (*ExecSandboxWindowResize) ProtoMessage() {} func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[53] + mi := &file_openshell_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3877,7 +3996,7 @@ func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecSandboxWindowResize.ProtoReflect.Descriptor instead. func (*ExecSandboxWindowResize) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{53} + return file_openshell_proto_rawDescGZIP(), []int{55} } func (x *ExecSandboxWindowResize) GetCols() uint32 { @@ -3914,7 +4033,7 @@ type SshSession struct { func (x *SshSession) Reset() { *x = SshSession{} - mi := &file_openshell_proto_msgTypes[54] + mi := &file_openshell_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3926,7 +4045,7 @@ func (x *SshSession) String() string { func (*SshSession) ProtoMessage() {} func (x *SshSession) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[54] + mi := &file_openshell_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3939,7 +4058,7 @@ func (x *SshSession) ProtoReflect() protoreflect.Message { // Deprecated: Use SshSession.ProtoReflect.Descriptor instead. func (*SshSession) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{54} + return file_openshell_proto_rawDescGZIP(), []int{56} } func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { @@ -4007,7 +4126,7 @@ type WatchSandboxRequest struct { func (x *WatchSandboxRequest) Reset() { *x = WatchSandboxRequest{} - mi := &file_openshell_proto_msgTypes[55] + mi := &file_openshell_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4019,7 +4138,7 @@ func (x *WatchSandboxRequest) String() string { func (*WatchSandboxRequest) ProtoMessage() {} func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[55] + mi := &file_openshell_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4032,7 +4151,7 @@ func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{55} + return file_openshell_proto_rawDescGZIP(), []int{57} } func (x *WatchSandboxRequest) GetId() string { @@ -4122,7 +4241,7 @@ type SandboxStreamEvent struct { func (x *SandboxStreamEvent) Reset() { *x = SandboxStreamEvent{} - mi := &file_openshell_proto_msgTypes[56] + mi := &file_openshell_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4134,7 +4253,7 @@ func (x *SandboxStreamEvent) String() string { func (*SandboxStreamEvent) ProtoMessage() {} func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[56] + mi := &file_openshell_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4147,7 +4266,7 @@ func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStreamEvent.ProtoReflect.Descriptor instead. func (*SandboxStreamEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{56} + return file_openshell_proto_rawDescGZIP(), []int{58} } func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { @@ -4260,7 +4379,7 @@ type SandboxLogLine struct { func (x *SandboxLogLine) Reset() { *x = SandboxLogLine{} - mi := &file_openshell_proto_msgTypes[57] + mi := &file_openshell_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4272,7 +4391,7 @@ func (x *SandboxLogLine) String() string { func (*SandboxLogLine) ProtoMessage() {} func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[57] + mi := &file_openshell_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4285,7 +4404,7 @@ func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxLogLine.ProtoReflect.Descriptor instead. func (*SandboxLogLine) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{57} + return file_openshell_proto_rawDescGZIP(), []int{59} } func (x *SandboxLogLine) GetSandboxId() string { @@ -4346,7 +4465,7 @@ type SandboxStreamWarning struct { func (x *SandboxStreamWarning) Reset() { *x = SandboxStreamWarning{} - mi := &file_openshell_proto_msgTypes[58] + mi := &file_openshell_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4358,7 +4477,7 @@ func (x *SandboxStreamWarning) String() string { func (*SandboxStreamWarning) ProtoMessage() {} func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[58] + mi := &file_openshell_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4371,7 +4490,7 @@ func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStreamWarning.ProtoReflect.Descriptor instead. func (*SandboxStreamWarning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{58} + return file_openshell_proto_rawDescGZIP(), []int{60} } func (x *SandboxStreamWarning) GetMessage() string { @@ -4393,7 +4512,7 @@ type CreateProviderRequest struct { func (x *CreateProviderRequest) Reset() { *x = CreateProviderRequest{} - mi := &file_openshell_proto_msgTypes[59] + mi := &file_openshell_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4405,7 +4524,7 @@ func (x *CreateProviderRequest) String() string { func (*CreateProviderRequest) ProtoMessage() {} func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[59] + mi := &file_openshell_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4418,7 +4537,7 @@ func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateProviderRequest.ProtoReflect.Descriptor instead. func (*CreateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{59} + return file_openshell_proto_rawDescGZIP(), []int{61} } func (x *CreateProviderRequest) GetProvider() *datamodelv1.Provider { @@ -4447,7 +4566,7 @@ type GetProviderRequest struct { func (x *GetProviderRequest) Reset() { *x = GetProviderRequest{} - mi := &file_openshell_proto_msgTypes[60] + mi := &file_openshell_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4459,7 +4578,7 @@ func (x *GetProviderRequest) String() string { func (*GetProviderRequest) ProtoMessage() {} func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[60] + mi := &file_openshell_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4472,7 +4591,7 @@ func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRequest.ProtoReflect.Descriptor instead. func (*GetProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{60} + return file_openshell_proto_rawDescGZIP(), []int{62} } func (x *GetProviderRequest) GetName() string { @@ -4504,7 +4623,7 @@ type ListProvidersRequest struct { func (x *ListProvidersRequest) Reset() { *x = ListProvidersRequest{} - mi := &file_openshell_proto_msgTypes[61] + mi := &file_openshell_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4516,7 +4635,7 @@ func (x *ListProvidersRequest) String() string { func (*ListProvidersRequest) ProtoMessage() {} func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[61] + mi := &file_openshell_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4529,7 +4648,7 @@ func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. func (*ListProvidersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{61} + return file_openshell_proto_rawDescGZIP(), []int{63} } func (x *ListProvidersRequest) GetLimit() uint32 { @@ -4575,7 +4694,7 @@ type UpdateProviderRequest struct { func (x *UpdateProviderRequest) Reset() { *x = UpdateProviderRequest{} - mi := &file_openshell_proto_msgTypes[62] + mi := &file_openshell_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4587,7 +4706,7 @@ func (x *UpdateProviderRequest) String() string { func (*UpdateProviderRequest) ProtoMessage() {} func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[62] + mi := &file_openshell_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4600,7 +4719,7 @@ func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{62} + return file_openshell_proto_rawDescGZIP(), []int{64} } func (x *UpdateProviderRequest) GetProvider() *datamodelv1.Provider { @@ -4636,7 +4755,7 @@ type DeleteProviderRequest struct { func (x *DeleteProviderRequest) Reset() { *x = DeleteProviderRequest{} - mi := &file_openshell_proto_msgTypes[63] + mi := &file_openshell_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4648,7 +4767,7 @@ func (x *DeleteProviderRequest) String() string { func (*DeleteProviderRequest) ProtoMessage() {} func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[63] + mi := &file_openshell_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4661,7 +4780,7 @@ func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{63} + return file_openshell_proto_rawDescGZIP(), []int{65} } func (x *DeleteProviderRequest) GetName() string { @@ -4688,7 +4807,7 @@ type ProviderResponse struct { func (x *ProviderResponse) Reset() { *x = ProviderResponse{} - mi := &file_openshell_proto_msgTypes[64] + mi := &file_openshell_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4700,7 +4819,7 @@ func (x *ProviderResponse) String() string { func (*ProviderResponse) ProtoMessage() {} func (x *ProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[64] + mi := &file_openshell_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4713,7 +4832,7 @@ func (x *ProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. func (*ProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{64} + return file_openshell_proto_rawDescGZIP(), []int{66} } func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { @@ -4733,7 +4852,7 @@ type ListProvidersResponse struct { func (x *ListProvidersResponse) Reset() { *x = ListProvidersResponse{} - mi := &file_openshell_proto_msgTypes[65] + mi := &file_openshell_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4745,7 +4864,7 @@ func (x *ListProvidersResponse) String() string { func (*ListProvidersResponse) ProtoMessage() {} func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[65] + mi := &file_openshell_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4758,7 +4877,7 @@ func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. func (*ListProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{65} + return file_openshell_proto_rawDescGZIP(), []int{67} } func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { @@ -4782,7 +4901,7 @@ type ListProviderProfilesRequest struct { func (x *ListProviderProfilesRequest) Reset() { *x = ListProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[66] + mi := &file_openshell_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4794,7 +4913,7 @@ func (x *ListProviderProfilesRequest) String() string { func (*ListProviderProfilesRequest) ProtoMessage() {} func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[66] + mi := &file_openshell_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4807,7 +4926,7 @@ func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{66} + return file_openshell_proto_rawDescGZIP(), []int{68} } func (x *ListProviderProfilesRequest) GetLimit() uint32 { @@ -4845,7 +4964,7 @@ type GetProviderProfileRequest struct { func (x *GetProviderProfileRequest) Reset() { *x = GetProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[67] + mi := &file_openshell_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4857,7 +4976,7 @@ func (x *GetProviderProfileRequest) String() string { func (*GetProviderProfileRequest) ProtoMessage() {} func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[67] + mi := &file_openshell_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4870,7 +4989,7 @@ func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{67} + return file_openshell_proto_rawDescGZIP(), []int{69} } func (x *GetProviderProfileRequest) GetId() string { @@ -4898,7 +5017,7 @@ type ProviderProfileImportItem struct { func (x *ProviderProfileImportItem) Reset() { *x = ProviderProfileImportItem{} - mi := &file_openshell_proto_msgTypes[68] + mi := &file_openshell_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4910,7 +5029,7 @@ func (x *ProviderProfileImportItem) String() string { func (*ProviderProfileImportItem) ProtoMessage() {} func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[68] + mi := &file_openshell_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4923,7 +5042,7 @@ func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{68} + return file_openshell_proto_rawDescGZIP(), []int{70} } func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { @@ -4954,7 +5073,7 @@ type ProviderProfileDiagnostic struct { func (x *ProviderProfileDiagnostic) Reset() { *x = ProviderProfileDiagnostic{} - mi := &file_openshell_proto_msgTypes[69] + mi := &file_openshell_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4966,7 +5085,7 @@ func (x *ProviderProfileDiagnostic) String() string { func (*ProviderProfileDiagnostic) ProtoMessage() {} func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[69] + mi := &file_openshell_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4979,7 +5098,7 @@ func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{69} + return file_openshell_proto_rawDescGZIP(), []int{71} } func (x *ProviderProfileDiagnostic) GetSource() string { @@ -5036,7 +5155,7 @@ type ProviderCredentialTokenGrantAudienceOverride struct { func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { *x = ProviderCredentialTokenGrantAudienceOverride{} - mi := &file_openshell_proto_msgTypes[70] + mi := &file_openshell_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5048,7 +5167,7 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[70] + mi := &file_openshell_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5061,7 +5180,7 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protorefle // Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{70} + return file_openshell_proto_rawDescGZIP(), []int{72} } func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { @@ -5126,7 +5245,7 @@ type ProviderCredentialTokenGrant struct { func (x *ProviderCredentialTokenGrant) Reset() { *x = ProviderCredentialTokenGrant{} - mi := &file_openshell_proto_msgTypes[71] + mi := &file_openshell_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5138,7 +5257,7 @@ func (x *ProviderCredentialTokenGrant) String() string { func (*ProviderCredentialTokenGrant) ProtoMessage() {} func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[71] + mi := &file_openshell_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5151,7 +5270,7 @@ func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{71} + return file_openshell_proto_rawDescGZIP(), []int{73} } func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { @@ -5222,7 +5341,7 @@ type ProviderProfileCredential struct { func (x *ProviderProfileCredential) Reset() { *x = ProviderProfileCredential{} - mi := &file_openshell_proto_msgTypes[72] + mi := &file_openshell_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5234,7 +5353,7 @@ func (x *ProviderProfileCredential) String() string { func (*ProviderProfileCredential) ProtoMessage() {} func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[72] + mi := &file_openshell_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5247,7 +5366,7 @@ func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{72} + return file_openshell_proto_rawDescGZIP(), []int{74} } func (x *ProviderProfileCredential) GetName() string { @@ -5332,7 +5451,7 @@ type ProviderCredentialRefreshMaterial struct { func (x *ProviderCredentialRefreshMaterial) Reset() { *x = ProviderCredentialRefreshMaterial{} - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5344,7 +5463,7 @@ func (x *ProviderCredentialRefreshMaterial) String() string { func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5357,7 +5476,7 @@ func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message // Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{73} + return file_openshell_proto_rawDescGZIP(), []int{75} } func (x *ProviderCredentialRefreshMaterial) GetName() string { @@ -5402,7 +5521,7 @@ type ProviderCredentialRefreshOutput struct { func (x *ProviderCredentialRefreshOutput) Reset() { *x = ProviderCredentialRefreshOutput{} - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5414,7 +5533,7 @@ func (x *ProviderCredentialRefreshOutput) String() string { func (*ProviderCredentialRefreshOutput) ProtoMessage() {} func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5427,7 +5546,7 @@ func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{74} + return file_openshell_proto_rawDescGZIP(), []int{76} } func (x *ProviderCredentialRefreshOutput) GetOutput() string { @@ -5459,7 +5578,7 @@ type ProviderCredentialRefresh struct { func (x *ProviderCredentialRefresh) Reset() { *x = ProviderCredentialRefresh{} - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5471,7 +5590,7 @@ func (x *ProviderCredentialRefresh) String() string { func (*ProviderCredentialRefresh) ProtoMessage() {} func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5484,7 +5603,7 @@ func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{75} + return file_openshell_proto_rawDescGZIP(), []int{77} } func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { @@ -5553,7 +5672,7 @@ type ProviderCredentialRefreshStatus struct { func (x *ProviderCredentialRefreshStatus) Reset() { *x = ProviderCredentialRefreshStatus{} - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5565,7 +5684,7 @@ func (x *ProviderCredentialRefreshStatus) String() string { func (*ProviderCredentialRefreshStatus) ProtoMessage() {} func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5578,7 +5697,7 @@ func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{76} + return file_openshell_proto_rawDescGZIP(), []int{78} } func (x *ProviderCredentialRefreshStatus) GetProviderName() string { @@ -5655,7 +5774,7 @@ type ProviderProfileDiscovery struct { func (x *ProviderProfileDiscovery) Reset() { *x = ProviderProfileDiscovery{} - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5667,7 +5786,7 @@ func (x *ProviderProfileDiscovery) String() string { func (*ProviderProfileDiscovery) ProtoMessage() {} func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5680,7 +5799,7 @@ func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{77} + return file_openshell_proto_rawDescGZIP(), []int{79} } func (x *ProviderProfileDiscovery) GetCredentials() []string { @@ -5719,7 +5838,7 @@ type StoredProviderCredentialRefreshState struct { func (x *StoredProviderCredentialRefreshState) Reset() { *x = StoredProviderCredentialRefreshState{} - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5731,7 +5850,7 @@ func (x *StoredProviderCredentialRefreshState) String() string { func (*StoredProviderCredentialRefreshState) ProtoMessage() {} func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5744,7 +5863,7 @@ func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Messa // Deprecated: Use StoredProviderCredentialRefreshState.ProtoReflect.Descriptor instead. func (*StoredProviderCredentialRefreshState) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{78} + return file_openshell_proto_rawDescGZIP(), []int{80} } func (x *StoredProviderCredentialRefreshState) GetMetadata() *datamodelv1.ObjectMeta { @@ -5878,7 +5997,7 @@ type GetProviderRefreshStatusRequest struct { func (x *GetProviderRefreshStatusRequest) Reset() { *x = GetProviderRefreshStatusRequest{} - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5890,7 +6009,7 @@ func (x *GetProviderRefreshStatusRequest) String() string { func (*GetProviderRefreshStatusRequest) ProtoMessage() {} func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5903,7 +6022,7 @@ func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{79} + return file_openshell_proto_rawDescGZIP(), []int{81} } func (x *GetProviderRefreshStatusRequest) GetProvider() string { @@ -5936,7 +6055,7 @@ type GetProviderRefreshStatusResponse struct { func (x *GetProviderRefreshStatusResponse) Reset() { *x = GetProviderRefreshStatusResponse{} - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5948,7 +6067,7 @@ func (x *GetProviderRefreshStatusResponse) String() string { func (*GetProviderRefreshStatusResponse) ProtoMessage() {} func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5961,7 +6080,7 @@ func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{80} + return file_openshell_proto_rawDescGZIP(), []int{82} } func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { @@ -5987,7 +6106,7 @@ type ConfigureProviderRefreshRequest struct { func (x *ConfigureProviderRefreshRequest) Reset() { *x = ConfigureProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5999,7 +6118,7 @@ func (x *ConfigureProviderRefreshRequest) String() string { func (*ConfigureProviderRefreshRequest) ProtoMessage() {} func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6012,7 +6131,7 @@ func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{81} + return file_openshell_proto_rawDescGZIP(), []int{83} } func (x *ConfigureProviderRefreshRequest) GetProvider() string { @@ -6073,7 +6192,7 @@ type ConfigureProviderRefreshResponse struct { func (x *ConfigureProviderRefreshResponse) Reset() { *x = ConfigureProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6085,7 +6204,7 @@ func (x *ConfigureProviderRefreshResponse) String() string { func (*ConfigureProviderRefreshResponse) ProtoMessage() {} func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6098,7 +6217,7 @@ func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{82} + return file_openshell_proto_rawDescGZIP(), []int{84} } func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -6120,7 +6239,7 @@ type RotateProviderCredentialRequest struct { func (x *RotateProviderCredentialRequest) Reset() { *x = RotateProviderCredentialRequest{} - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6132,7 +6251,7 @@ func (x *RotateProviderCredentialRequest) String() string { func (*RotateProviderCredentialRequest) ProtoMessage() {} func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6145,7 +6264,7 @@ func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{83} + return file_openshell_proto_rawDescGZIP(), []int{85} } func (x *RotateProviderCredentialRequest) GetProvider() string { @@ -6178,7 +6297,7 @@ type RotateProviderCredentialResponse struct { func (x *RotateProviderCredentialResponse) Reset() { *x = RotateProviderCredentialResponse{} - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6190,7 +6309,7 @@ func (x *RotateProviderCredentialResponse) String() string { func (*RotateProviderCredentialResponse) ProtoMessage() {} func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6203,7 +6322,7 @@ func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{84} + return file_openshell_proto_rawDescGZIP(), []int{86} } func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -6225,7 +6344,7 @@ type DeleteProviderRefreshRequest struct { func (x *DeleteProviderRefreshRequest) Reset() { *x = DeleteProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6237,7 +6356,7 @@ func (x *DeleteProviderRefreshRequest) String() string { func (*DeleteProviderRefreshRequest) ProtoMessage() {} func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6250,7 +6369,7 @@ func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{85} + return file_openshell_proto_rawDescGZIP(), []int{87} } func (x *DeleteProviderRefreshRequest) GetProvider() string { @@ -6283,7 +6402,7 @@ type DeleteProviderRefreshResponse struct { func (x *DeleteProviderRefreshResponse) Reset() { *x = DeleteProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6295,7 +6414,7 @@ func (x *DeleteProviderRefreshResponse) String() string { func (*DeleteProviderRefreshResponse) ProtoMessage() {} func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6308,7 +6427,7 @@ func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{86} + return file_openshell_proto_rawDescGZIP(), []int{88} } func (x *DeleteProviderRefreshResponse) GetDeleted() bool { @@ -6348,7 +6467,7 @@ type ProviderProfile struct { func (x *ProviderProfile) Reset() { *x = ProviderProfile{} - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6360,7 +6479,7 @@ func (x *ProviderProfile) String() string { func (*ProviderProfile) ProtoMessage() {} func (x *ProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6373,7 +6492,7 @@ func (x *ProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. func (*ProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{87} + return file_openshell_proto_rawDescGZIP(), []int{89} } func (x *ProviderProfile) GetId() string { @@ -6478,7 +6597,7 @@ type StoredProviderProfile struct { func (x *StoredProviderProfile) Reset() { *x = StoredProviderProfile{} - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6490,7 +6609,7 @@ func (x *StoredProviderProfile) String() string { func (*StoredProviderProfile) ProtoMessage() {} func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6503,7 +6622,7 @@ func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredProviderProfile.ProtoReflect.Descriptor instead. func (*StoredProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{88} + return file_openshell_proto_rawDescGZIP(), []int{90} } func (x *StoredProviderProfile) GetMetadata() *datamodelv1.ObjectMeta { @@ -6530,7 +6649,7 @@ type ProviderProfileResponse struct { func (x *ProviderProfileResponse) Reset() { *x = ProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6542,7 +6661,7 @@ func (x *ProviderProfileResponse) String() string { func (*ProviderProfileResponse) ProtoMessage() {} func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6555,7 +6674,7 @@ func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{89} + return file_openshell_proto_rawDescGZIP(), []int{91} } func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { @@ -6575,7 +6694,7 @@ type ListProviderProfilesResponse struct { func (x *ListProviderProfilesResponse) Reset() { *x = ListProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6587,7 +6706,7 @@ func (x *ListProviderProfilesResponse) String() string { func (*ListProviderProfilesResponse) ProtoMessage() {} func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6600,7 +6719,7 @@ func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{90} + return file_openshell_proto_rawDescGZIP(), []int{92} } func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { @@ -6623,7 +6742,7 @@ type ImportProviderProfilesRequest struct { func (x *ImportProviderProfilesRequest) Reset() { *x = ImportProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6635,7 +6754,7 @@ func (x *ImportProviderProfilesRequest) String() string { func (*ImportProviderProfilesRequest) ProtoMessage() {} func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6648,7 +6767,7 @@ func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{91} + return file_openshell_proto_rawDescGZIP(), []int{93} } func (x *ImportProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { @@ -6677,7 +6796,7 @@ type ImportProviderProfilesResponse struct { func (x *ImportProviderProfilesResponse) Reset() { *x = ImportProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6689,7 +6808,7 @@ func (x *ImportProviderProfilesResponse) String() string { func (*ImportProviderProfilesResponse) ProtoMessage() {} func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6702,7 +6821,7 @@ func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{92} + return file_openshell_proto_rawDescGZIP(), []int{94} } func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -6746,7 +6865,7 @@ type UpdateProviderProfilesRequest struct { func (x *UpdateProviderProfilesRequest) Reset() { *x = UpdateProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6758,7 +6877,7 @@ func (x *UpdateProviderProfilesRequest) String() string { func (*UpdateProviderProfilesRequest) ProtoMessage() {} func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6771,7 +6890,7 @@ func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{93} + return file_openshell_proto_rawDescGZIP(), []int{95} } func (x *UpdateProviderProfilesRequest) GetProfile() *ProviderProfileImportItem { @@ -6814,7 +6933,7 @@ type UpdateProviderProfilesResponse struct { func (x *UpdateProviderProfilesResponse) Reset() { *x = UpdateProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6826,7 +6945,7 @@ func (x *UpdateProviderProfilesResponse) String() string { func (*UpdateProviderProfilesResponse) ProtoMessage() {} func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6839,7 +6958,7 @@ func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{94} + return file_openshell_proto_rawDescGZIP(), []int{96} } func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -6876,7 +6995,7 @@ type LintProviderProfilesRequest struct { func (x *LintProviderProfilesRequest) Reset() { *x = LintProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6888,7 +7007,7 @@ func (x *LintProviderProfilesRequest) String() string { func (*LintProviderProfilesRequest) ProtoMessage() {} func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6901,7 +7020,7 @@ func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{95} + return file_openshell_proto_rawDescGZIP(), []int{97} } func (x *LintProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { @@ -6929,7 +7048,7 @@ type LintProviderProfilesResponse struct { func (x *LintProviderProfilesResponse) Reset() { *x = LintProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6941,7 +7060,7 @@ func (x *LintProviderProfilesResponse) String() string { func (*LintProviderProfilesResponse) ProtoMessage() {} func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6954,7 +7073,7 @@ func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{96} + return file_openshell_proto_rawDescGZIP(), []int{98} } func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -6981,7 +7100,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6993,7 +7112,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7006,7 +7125,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{97} + return file_openshell_proto_rawDescGZIP(), []int{99} } func (x *DeleteProviderResponse) GetDeleted() bool { @@ -7029,7 +7148,7 @@ type DeleteProviderProfileRequest struct { func (x *DeleteProviderProfileRequest) Reset() { *x = DeleteProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7041,7 +7160,7 @@ func (x *DeleteProviderProfileRequest) String() string { func (*DeleteProviderProfileRequest) ProtoMessage() {} func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7054,7 +7173,7 @@ func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{98} + return file_openshell_proto_rawDescGZIP(), []int{100} } func (x *DeleteProviderProfileRequest) GetId() string { @@ -7081,7 +7200,7 @@ type DeleteProviderProfileResponse struct { func (x *DeleteProviderProfileResponse) Reset() { *x = DeleteProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7093,7 +7212,7 @@ func (x *DeleteProviderProfileResponse) String() string { func (*DeleteProviderProfileResponse) ProtoMessage() {} func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7106,7 +7225,7 @@ func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{99} + return file_openshell_proto_rawDescGZIP(), []int{101} } func (x *DeleteProviderProfileResponse) GetDeleted() bool { @@ -7131,7 +7250,7 @@ type GetSandboxProviderEnvironmentRequest struct { func (x *GetSandboxProviderEnvironmentRequest) Reset() { *x = GetSandboxProviderEnvironmentRequest{} - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7143,7 +7262,7 @@ func (x *GetSandboxProviderEnvironmentRequest) String() string { func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7156,7 +7275,7 @@ func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{100} + return file_openshell_proto_rawDescGZIP(), []int{102} } func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { @@ -7185,7 +7304,7 @@ type StaticCredentialEndpointBinding struct { func (x *StaticCredentialEndpointBinding) Reset() { *x = StaticCredentialEndpointBinding{} - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7197,7 +7316,7 @@ func (x *StaticCredentialEndpointBinding) String() string { func (*StaticCredentialEndpointBinding) ProtoMessage() {} func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7210,7 +7329,7 @@ func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialEndpointBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialEndpointBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{101} + return file_openshell_proto_rawDescGZIP(), []int{103} } func (x *StaticCredentialEndpointBinding) GetHost() string { @@ -7248,7 +7367,7 @@ type StaticCredentialBinding struct { func (x *StaticCredentialBinding) Reset() { *x = StaticCredentialBinding{} - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7260,7 +7379,7 @@ func (x *StaticCredentialBinding) String() string { func (*StaticCredentialBinding) ProtoMessage() {} func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7273,7 +7392,7 @@ func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{102} + return file_openshell_proto_rawDescGZIP(), []int{104} } func (x *StaticCredentialBinding) GetEndpoints() []*StaticCredentialEndpointBinding { @@ -7317,7 +7436,7 @@ type GetSandboxProviderEnvironmentResponse struct { func (x *GetSandboxProviderEnvironmentResponse) Reset() { *x = GetSandboxProviderEnvironmentResponse{} - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7329,7 +7448,7 @@ func (x *GetSandboxProviderEnvironmentResponse) String() string { func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7342,7 +7461,7 @@ func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{103} + return file_openshell_proto_rawDescGZIP(), []int{105} } func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { @@ -7434,7 +7553,7 @@ type UpdateConfigRequest struct { func (x *UpdateConfigRequest) Reset() { *x = UpdateConfigRequest{} - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7446,7 +7565,7 @@ func (x *UpdateConfigRequest) String() string { func (*UpdateConfigRequest) ProtoMessage() {} func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7459,7 +7578,7 @@ func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{104} + return file_openshell_proto_rawDescGZIP(), []int{106} } func (x *UpdateConfigRequest) GetName() string { @@ -7549,7 +7668,7 @@ type PolicyMergeOperation struct { func (x *PolicyMergeOperation) Reset() { *x = PolicyMergeOperation{} - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7561,7 +7680,7 @@ func (x *PolicyMergeOperation) String() string { func (*PolicyMergeOperation) ProtoMessage() {} func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7574,7 +7693,7 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{105} + return file_openshell_proto_rawDescGZIP(), []int{107} } func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { @@ -7688,7 +7807,7 @@ type AddNetworkRule struct { func (x *AddNetworkRule) Reset() { *x = AddNetworkRule{} - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7700,7 +7819,7 @@ func (x *AddNetworkRule) String() string { func (*AddNetworkRule) ProtoMessage() {} func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7713,7 +7832,7 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. func (*AddNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{106} + return file_openshell_proto_rawDescGZIP(), []int{108} } func (x *AddNetworkRule) GetRuleName() string { @@ -7741,7 +7860,7 @@ type RemoveNetworkEndpoint struct { func (x *RemoveNetworkEndpoint) Reset() { *x = RemoveNetworkEndpoint{} - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7753,7 +7872,7 @@ func (x *RemoveNetworkEndpoint) String() string { func (*RemoveNetworkEndpoint) ProtoMessage() {} func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7766,7 +7885,7 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{107} + return file_openshell_proto_rawDescGZIP(), []int{109} } func (x *RemoveNetworkEndpoint) GetRuleName() string { @@ -7799,7 +7918,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7811,7 +7930,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7824,7 +7943,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{108} + return file_openshell_proto_rawDescGZIP(), []int{110} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -7845,7 +7964,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7857,7 +7976,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7870,7 +7989,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{109} + return file_openshell_proto_rawDescGZIP(), []int{111} } func (x *AddDenyRules) GetHost() string { @@ -7905,7 +8024,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7917,7 +8036,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7930,7 +8049,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{110} + return file_openshell_proto_rawDescGZIP(), []int{112} } func (x *AddAllowRules) GetHost() string { @@ -7964,7 +8083,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7976,7 +8095,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7989,7 +8108,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{111} + return file_openshell_proto_rawDescGZIP(), []int{113} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -8025,7 +8144,7 @@ type UpdateConfigResponse struct { func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8037,7 +8156,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8050,7 +8169,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{112} + return file_openshell_proto_rawDescGZIP(), []int{114} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -8105,7 +8224,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8117,7 +8236,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8130,7 +8249,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{113} + return file_openshell_proto_rawDescGZIP(), []int{115} } func (x *GetSandboxPolicyStatusRequest) GetName() string { @@ -8174,7 +8293,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8186,7 +8305,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8199,7 +8318,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{114} + return file_openshell_proto_rawDescGZIP(), []int{116} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -8233,7 +8352,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8245,7 +8364,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8258,7 +8377,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{115} + return file_openshell_proto_rawDescGZIP(), []int{117} } func (x *ListSandboxPoliciesRequest) GetName() string { @@ -8306,7 +8425,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8318,7 +8437,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8331,7 +8450,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{116} + return file_openshell_proto_rawDescGZIP(), []int{118} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -8358,7 +8477,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8370,7 +8489,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8383,7 +8502,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{117} + return file_openshell_proto_rawDescGZIP(), []int{119} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -8423,7 +8542,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8435,7 +8554,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8448,7 +8567,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{118} + return file_openshell_proto_rawDescGZIP(), []int{120} } // A versioned policy revision with metadata. @@ -8476,7 +8595,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8488,7 +8607,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8501,7 +8620,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{119} + return file_openshell_proto_rawDescGZIP(), []int{121} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -8581,7 +8700,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8593,7 +8712,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8606,7 +8725,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{120} + return file_openshell_proto_rawDescGZIP(), []int{122} } func (x *GetSandboxLogsRequest) GetSandboxId() string { @@ -8664,7 +8783,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8676,7 +8795,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8689,7 +8808,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{121} + return file_openshell_proto_rawDescGZIP(), []int{123} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -8715,7 +8834,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8727,7 +8846,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8740,7 +8859,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{122} + return file_openshell_proto_rawDescGZIP(), []int{124} } // Get sandbox logs response. @@ -8756,7 +8875,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8768,7 +8887,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8781,7 +8900,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{123} + return file_openshell_proto_rawDescGZIP(), []int{125} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -8814,7 +8933,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8826,7 +8945,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8839,7 +8958,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{124} + return file_openshell_proto_rawDescGZIP(), []int{126} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -8930,7 +9049,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8942,7 +9061,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8955,7 +9074,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{125} + return file_openshell_proto_rawDescGZIP(), []int{127} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -9057,7 +9176,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9069,7 +9188,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9082,7 +9201,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{126} + return file_openshell_proto_rawDescGZIP(), []int{128} } func (x *SupervisorHello) GetSandboxId() string { @@ -9112,7 +9231,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9124,7 +9243,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9137,7 +9256,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{127} + return file_openshell_proto_rawDescGZIP(), []int{129} } func (x *SessionAccepted) GetSessionId() string { @@ -9165,7 +9284,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9177,7 +9296,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9190,7 +9309,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{128} + return file_openshell_proto_rawDescGZIP(), []int{130} } func (x *SessionRejected) GetReason() string { @@ -9209,7 +9328,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9221,7 +9340,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9234,7 +9353,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{129} + return file_openshell_proto_rawDescGZIP(), []int{131} } // Gateway heartbeat. @@ -9246,7 +9365,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9258,7 +9377,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9271,7 +9390,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{130} + return file_openshell_proto_rawDescGZIP(), []int{132} } // Gateway requests the supervisor to open a relay channel. @@ -9300,7 +9419,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9312,7 +9431,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9325,7 +9444,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{131} + return file_openshell_proto_rawDescGZIP(), []int{133} } func (x *RelayOpen) GetChannelId() string { @@ -9392,7 +9511,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9404,7 +9523,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9417,7 +9536,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{132} + return file_openshell_proto_rawDescGZIP(), []int{134} } // TCP target dialed by the supervisor from inside the sandbox. @@ -9433,7 +9552,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9445,7 +9564,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9458,7 +9577,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{135} } func (x *TcpRelayTarget) GetHost() string { @@ -9486,7 +9605,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9498,7 +9617,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9511,7 +9630,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{136} } func (x *RelayInit) GetChannelId() string { @@ -9538,7 +9657,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9550,7 +9669,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9563,7 +9682,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{137} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -9622,7 +9741,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9634,7 +9753,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9647,7 +9766,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *RelayOpenResult) GetChannelId() string { @@ -9684,7 +9803,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9696,7 +9815,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9709,7 +9828,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *RelayClose) GetChannelId() string { @@ -9743,7 +9862,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9755,7 +9874,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9768,7 +9887,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *L7RequestSample) GetMethod() string { @@ -9842,7 +9961,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9854,7 +9973,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9867,7 +9986,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *DenialSummary) GetSandboxId() string { @@ -10002,7 +10121,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10014,7 +10133,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10027,7 +10146,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{142} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -10060,7 +10179,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10072,7 +10191,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10085,7 +10204,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -10159,7 +10278,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10171,7 +10290,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10184,7 +10303,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *PolicyChunk) GetId() string { @@ -10330,7 +10449,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10342,7 +10461,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10355,7 +10474,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -10413,7 +10532,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10425,7 +10544,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10438,7 +10557,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{146} } func (x *SubmitPolicyAnalysisRequest) GetSummaries() []*DenialSummary { @@ -10501,7 +10620,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10513,7 +10632,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10526,7 +10645,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -10572,7 +10691,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10584,7 +10703,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10597,7 +10716,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *GetDraftPolicyRequest) GetName() string { @@ -10637,7 +10756,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10649,7 +10768,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10662,7 +10781,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -10708,7 +10827,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10720,7 +10839,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10733,7 +10852,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{150} } func (x *ApproveDraftChunkRequest) GetName() string { @@ -10769,7 +10888,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10781,7 +10900,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10794,7 +10913,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -10828,7 +10947,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10840,7 +10959,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10853,7 +10972,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *RejectDraftChunkRequest) GetName() string { @@ -10892,7 +11011,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10904,7 +11023,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10917,7 +11036,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{153} } // Approve all pending chunks. @@ -10935,7 +11054,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10947,7 +11066,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10960,7 +11079,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{154} } func (x *ApproveAllDraftChunksRequest) GetName() string { @@ -11000,7 +11119,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11012,7 +11131,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11025,7 +11144,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -11073,7 +11192,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11085,7 +11204,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11098,7 +11217,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{156} } func (x *EditDraftChunkRequest) GetName() string { @@ -11137,7 +11256,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11149,7 +11268,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11162,7 +11281,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{157} } // Reverse an approval (remove merged rule from active policy). @@ -11180,7 +11299,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11192,7 +11311,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11205,7 +11324,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *UndoDraftChunkRequest) GetName() string { @@ -11241,7 +11360,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11253,7 +11372,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11266,7 +11385,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{159} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -11296,7 +11415,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11308,7 +11427,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11321,7 +11440,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{160} } func (x *ClearDraftChunksRequest) GetName() string { @@ -11348,7 +11467,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11360,7 +11479,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11373,7 +11492,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -11396,7 +11515,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11408,7 +11527,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11421,7 +11540,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *GetDraftHistoryRequest) GetName() string { @@ -11455,7 +11574,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11467,7 +11586,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11480,7 +11599,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{163} } func (x *DraftHistoryEntry) GetTimestampMs() int64 { @@ -11521,7 +11640,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11533,7 +11652,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11546,7 +11665,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -11575,7 +11694,7 @@ type PolicyRevisionPayload struct { func (x *PolicyRevisionPayload) Reset() { *x = PolicyRevisionPayload{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11587,7 +11706,7 @@ func (x *PolicyRevisionPayload) String() string { func (*PolicyRevisionPayload) ProtoMessage() {} func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11600,7 +11719,7 @@ func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRevisionPayload.ProtoReflect.Descriptor instead. func (*PolicyRevisionPayload) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{165} } func (x *PolicyRevisionPayload) GetPolicy() *sandboxv1.SandboxPolicy { @@ -11673,7 +11792,7 @@ type DraftChunkPayload struct { func (x *DraftChunkPayload) Reset() { *x = DraftChunkPayload{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11685,7 +11804,7 @@ func (x *DraftChunkPayload) String() string { func (*DraftChunkPayload) ProtoMessage() {} func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11698,7 +11817,7 @@ func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkPayload.ProtoReflect.Descriptor instead. func (*DraftChunkPayload) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{166} } func (x *DraftChunkPayload) GetRuleName() string { @@ -11804,7 +11923,7 @@ type StoredPolicyRevision struct { func (x *StoredPolicyRevision) Reset() { *x = StoredPolicyRevision{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11816,7 +11935,7 @@ func (x *StoredPolicyRevision) String() string { func (*StoredPolicyRevision) ProtoMessage() {} func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11829,7 +11948,7 @@ func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredPolicyRevision.ProtoReflect.Descriptor instead. func (*StoredPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *StoredPolicyRevision) GetId() string { @@ -11932,7 +12051,7 @@ type StoredDraftChunk struct { func (x *StoredDraftChunk) Reset() { *x = StoredDraftChunk{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11944,7 +12063,7 @@ func (x *StoredDraftChunk) String() string { func (*StoredDraftChunk) ProtoMessage() {} func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11957,7 +12076,7 @@ func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredDraftChunk.ProtoReflect.Descriptor instead. func (*StoredDraftChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{168} } func (x *StoredDraftChunk) GetId() string { @@ -12106,7 +12225,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12118,7 +12237,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12131,7 +12250,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *CreateWorkspaceRequest) GetName() string { @@ -12158,7 +12277,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12170,7 +12289,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12183,7 +12302,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{170} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -12204,7 +12323,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12216,7 +12335,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12229,7 +12348,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{171} } func (x *GetWorkspaceRequest) GetName() string { @@ -12249,7 +12368,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12261,7 +12380,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12274,7 +12393,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{172} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -12297,7 +12416,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12309,7 +12428,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12322,7 +12441,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{173} } func (x *ListWorkspacesRequest) GetLimit() uint32 { @@ -12356,7 +12475,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12368,7 +12487,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12381,7 +12500,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -12402,7 +12521,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12414,7 +12533,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12427,7 +12546,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{175} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -12447,7 +12566,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12459,7 +12578,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12472,7 +12591,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *DeleteWorkspaceResponse) GetDeleted() bool { @@ -12496,7 +12615,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12508,7 +12627,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12521,7 +12640,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{177} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -12560,7 +12679,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12572,7 +12691,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12585,7 +12704,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *AddWorkspaceMemberRequest) GetWorkspace() string { @@ -12619,7 +12738,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12631,7 +12750,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12644,7 +12763,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{179} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -12667,7 +12786,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12679,7 +12798,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12692,7 +12811,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{180} } func (x *RemoveWorkspaceMemberRequest) GetWorkspace() string { @@ -12719,7 +12838,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12731,7 +12850,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12744,7 +12863,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *RemoveWorkspaceMemberResponse) GetRemoved() bool { @@ -12767,7 +12886,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12779,7 +12898,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12792,7 +12911,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{182} } func (x *ListWorkspaceMembersRequest) GetWorkspace() string { @@ -12826,7 +12945,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12838,7 +12957,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12851,7 +12970,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{183} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -13003,6 +13122,12 @@ const file_openshell_proto_rawDesc = "" + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"H\n" + "\x14DeleteSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"I\n" + + "\x15SuspendSandboxRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"H\n" + + "\x14ResumeSandboxRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"B\n" + "\x0fSandboxResponse\x12/\n" + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\"L\n" + @@ -13846,14 +13971,17 @@ const file_openshell_proto_rawDesc = "" + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\rR\x06offset\"W\n" + "\x1cListWorkspaceMembersResponse\x127\n" + - "\amembers\x18\x01 \x03(\v2\x1d.openshell.v1.WorkspaceMemberR\amembers*\xb6\x01\n" + + "\amembers\x18\x01 \x03(\v2\x1d.openshell.v1.WorkspaceMemberR\amembers*\x8d\x02\n" + "\fSandboxPhase\x12\x1d\n" + "\x19SANDBOX_PHASE_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aSANDBOX_PHASE_PROVISIONING\x10\x01\x12\x17\n" + "\x13SANDBOX_PHASE_READY\x10\x02\x12\x17\n" + "\x13SANDBOX_PHASE_ERROR\x10\x03\x12\x1a\n" + "\x16SANDBOX_PHASE_DELETING\x10\x04\x12\x19\n" + - "\x15SANDBOX_PHASE_UNKNOWN\x10\x05*\xc3\x03\n" + + "\x15SANDBOX_PHASE_UNKNOWN\x10\x05\x12\x1c\n" + + "\x18SANDBOX_PHASE_SUSPENDING\x10\x06\x12\x1b\n" + + "\x17SANDBOX_PHASE_SUSPENDED\x10\a\x12\x1a\n" + + "\x16SANDBOX_PHASE_RESUMING\x10\b*\xc3\x03\n" + "!ProviderCredentialRefreshStrategy\x124\n" + "0PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED\x10\x00\x12/\n" + "+PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC\x10\x01\x121\n" + @@ -13885,7 +14013,7 @@ const file_openshell_proto_rawDesc = "" + "\rWorkspaceRole\x12\x1e\n" + "\x1aWORKSPACE_ROLE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13WORKSPACE_ROLE_USER\x10\x01\x12\x18\n" + - "\x14WORKSPACE_ROLE_ADMIN\x10\x022\xacB\n" + + "\x14WORKSPACE_ROLE_ADMIN\x10\x022\x9cD\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -13907,6 +14035,10 @@ const file_openshell_proto_rawDesc = "" + "\x15DetachSandboxProvider\x12*.openshell.v1.DetachSandboxProviderRequest\x1a+.openshell.v1.DetachSandboxProviderResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12{\n" + "\rDeleteSandbox\x12\".openshell.v1.DeleteSandboxRequest\x1a#.openshell.v1.DeleteSandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12w\n" + + "\x0eSuspendSandbox\x12#.openshell.v1.SuspendSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12u\n" + + "\rResumeSandbox\x12\".openshell.v1.ResumeSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12\x84\x01\n" + "\x10CreateSshSession\x12%.openshell.v1.CreateSshSessionRequest\x1a&.openshell.v1.CreateSshSessionResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12}\n" + @@ -14032,7 +14164,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 206) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 208) var file_openshell_proto_goTypes = []any{ (SandboxPhase)(0), // 0: openshell.v1.SandboxPhase (ProviderCredentialRefreshStrategy)(0), // 1: openshell.v1.ProviderCredentialRefreshStrategy @@ -14067,352 +14199,354 @@ var file_openshell_proto_goTypes = []any{ (*AttachSandboxProviderRequest)(nil), // 30: openshell.v1.AttachSandboxProviderRequest (*DetachSandboxProviderRequest)(nil), // 31: openshell.v1.DetachSandboxProviderRequest (*DeleteSandboxRequest)(nil), // 32: openshell.v1.DeleteSandboxRequest - (*SandboxResponse)(nil), // 33: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 34: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 35: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 36: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 37: openshell.v1.DetachSandboxProviderResponse - (*DeleteSandboxResponse)(nil), // 38: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 39: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 40: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 41: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 42: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 43: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 44: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 45: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 46: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 47: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 48: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 49: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 50: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 51: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 52: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 53: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 54: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 55: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 56: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 57: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 58: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 59: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 60: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 61: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 62: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 63: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 64: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 65: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 66: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 67: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 68: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 69: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 70: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 71: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 72: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 73: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 74: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 75: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 76: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrant)(nil), // 77: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 78: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 79: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 80: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 81: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 82: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 83: openshell.v1.ProviderProfileDiscovery - (*StoredProviderCredentialRefreshState)(nil), // 84: openshell.v1.StoredProviderCredentialRefreshState - (*GetProviderRefreshStatusRequest)(nil), // 85: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 86: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 87: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 88: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 89: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 90: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 91: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 92: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 93: openshell.v1.ProviderProfile - (*StoredProviderProfile)(nil), // 94: openshell.v1.StoredProviderProfile - (*ProviderProfileResponse)(nil), // 95: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 96: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 97: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 98: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 99: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 100: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 101: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 102: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 103: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 104: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 105: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 106: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 107: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 108: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 109: openshell.v1.GetSandboxProviderEnvironmentResponse - (*UpdateConfigRequest)(nil), // 110: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 111: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 112: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 113: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 114: openshell.v1.RemoveNetworkRule - (*AddDenyRules)(nil), // 115: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 116: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 117: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 118: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 119: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 120: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 121: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 122: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 123: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 124: openshell.v1.ReportPolicyStatusResponse - (*SandboxPolicyRevision)(nil), // 125: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 126: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 127: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 128: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 129: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 130: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 131: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 132: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 133: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 134: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 135: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 136: openshell.v1.GatewayHeartbeat - (*RelayOpen)(nil), // 137: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 138: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 139: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 140: openshell.v1.RelayInit - (*RelayFrame)(nil), // 141: openshell.v1.RelayFrame - (*RelayOpenResult)(nil), // 142: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 143: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 144: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 145: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 146: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 147: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 148: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 149: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 150: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 151: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 152: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 153: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 154: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 155: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 156: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 157: openshell.v1.RejectDraftChunkResponse - (*ApproveAllDraftChunksRequest)(nil), // 158: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 159: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 160: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 161: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 162: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 163: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 164: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 165: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 166: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 167: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 168: openshell.v1.GetDraftHistoryResponse - (*PolicyRevisionPayload)(nil), // 169: openshell.v1.PolicyRevisionPayload - (*DraftChunkPayload)(nil), // 170: openshell.v1.DraftChunkPayload - (*StoredPolicyRevision)(nil), // 171: openshell.v1.StoredPolicyRevision - (*StoredDraftChunk)(nil), // 172: openshell.v1.StoredDraftChunk - (*CreateWorkspaceRequest)(nil), // 173: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 174: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 175: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 176: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 177: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 178: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 179: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 180: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 181: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 182: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 183: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 184: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 185: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 186: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 187: openshell.v1.ListWorkspaceMembersResponse - nil, // 188: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 189: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 190: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 191: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 192: openshell.v1.PlatformEvent.MetadataEntry - nil, // 193: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 194: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 195: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 196: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 197: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - nil, // 198: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - nil, // 199: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - nil, // 200: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 201: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 202: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 203: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - nil, // 204: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 205: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 206: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 207: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 208: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 209: openshell.v1.PolicyRevisionPayload.ProvenanceEntry - nil, // 210: openshell.v1.StoredPolicyRevision.ProvenanceEntry - nil, // 211: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*datamodelv1.ObjectMeta)(nil), // 212: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 213: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 214: google.protobuf.Struct - (*datamodelv1.Provider)(nil), // 215: openshell.datamodel.v1.Provider - (*sandboxv1.NetworkEndpoint)(nil), // 216: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 217: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 218: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 219: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 220: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 221: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 222: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 223: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 224: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 225: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 226: openshell.sandbox.v1.GetGatewayConfigResponse + (*SuspendSandboxRequest)(nil), // 33: openshell.v1.SuspendSandboxRequest + (*ResumeSandboxRequest)(nil), // 34: openshell.v1.ResumeSandboxRequest + (*SandboxResponse)(nil), // 35: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 36: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 37: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 38: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 39: openshell.v1.DetachSandboxProviderResponse + (*DeleteSandboxResponse)(nil), // 40: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 41: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 42: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 43: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 44: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 45: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 46: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 47: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 48: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 49: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 50: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 51: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 52: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 53: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 54: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 55: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 56: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 57: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 58: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 59: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 60: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 61: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 62: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 63: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 64: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 65: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 66: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 67: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 68: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 69: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 70: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 71: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 72: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 73: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 74: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 75: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 76: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 77: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 78: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrant)(nil), // 79: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 80: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 81: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 82: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 83: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 84: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 85: openshell.v1.ProviderProfileDiscovery + (*StoredProviderCredentialRefreshState)(nil), // 86: openshell.v1.StoredProviderCredentialRefreshState + (*GetProviderRefreshStatusRequest)(nil), // 87: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 88: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 89: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 90: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 91: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 92: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 93: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 94: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 95: openshell.v1.ProviderProfile + (*StoredProviderProfile)(nil), // 96: openshell.v1.StoredProviderProfile + (*ProviderProfileResponse)(nil), // 97: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 98: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 99: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 100: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 101: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 102: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 103: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 104: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 105: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 106: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 107: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 108: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 109: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 110: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 111: openshell.v1.GetSandboxProviderEnvironmentResponse + (*UpdateConfigRequest)(nil), // 112: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 113: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 114: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 115: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 116: openshell.v1.RemoveNetworkRule + (*AddDenyRules)(nil), // 117: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 118: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 119: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 120: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 121: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 122: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 123: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 124: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 125: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 126: openshell.v1.ReportPolicyStatusResponse + (*SandboxPolicyRevision)(nil), // 127: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 128: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 129: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 130: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 131: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 132: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 133: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 134: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 135: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 136: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 137: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 138: openshell.v1.GatewayHeartbeat + (*RelayOpen)(nil), // 139: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 140: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 141: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 142: openshell.v1.RelayInit + (*RelayFrame)(nil), // 143: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 144: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 145: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 146: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 147: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 148: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 149: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 150: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 151: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 152: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 153: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 154: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 155: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 156: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 157: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 158: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 159: openshell.v1.RejectDraftChunkResponse + (*ApproveAllDraftChunksRequest)(nil), // 160: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 161: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 162: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 163: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 164: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 165: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 166: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 167: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 168: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 169: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 170: openshell.v1.GetDraftHistoryResponse + (*PolicyRevisionPayload)(nil), // 171: openshell.v1.PolicyRevisionPayload + (*DraftChunkPayload)(nil), // 172: openshell.v1.DraftChunkPayload + (*StoredPolicyRevision)(nil), // 173: openshell.v1.StoredPolicyRevision + (*StoredDraftChunk)(nil), // 174: openshell.v1.StoredDraftChunk + (*CreateWorkspaceRequest)(nil), // 175: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 176: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 177: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 178: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 179: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 180: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 181: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 182: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 183: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 184: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 185: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 186: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 187: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 188: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 189: openshell.v1.ListWorkspaceMembersResponse + nil, // 190: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 191: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 192: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 193: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 194: openshell.v1.PlatformEvent.MetadataEntry + nil, // 195: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 196: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 197: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 198: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 199: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + nil, // 200: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + nil, // 201: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + nil, // 202: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 203: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 204: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 205: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + nil, // 206: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 207: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 208: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 209: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 210: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 211: openshell.v1.PolicyRevisionPayload.ProvenanceEntry + nil, // 212: openshell.v1.StoredPolicyRevision.ProvenanceEntry + nil, // 213: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*datamodelv1.ObjectMeta)(nil), // 214: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 215: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 216: google.protobuf.Struct + (*datamodelv1.Provider)(nil), // 217: openshell.datamodel.v1.Provider + (*sandboxv1.NetworkEndpoint)(nil), // 218: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 219: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 220: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 221: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 222: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 223: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 224: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 225: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 226: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 227: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 228: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ 4, // 0: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 4, // 1: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus 16, // 2: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo 17, // 3: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 212, // 4: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 214, // 4: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 19, // 5: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec 23, // 6: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 188, // 7: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 190, // 7: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry 22, // 8: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 213, // 9: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 215, // 9: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy 20, // 10: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements 21, // 11: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 189, // 12: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 190, // 13: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 191, // 14: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 214, // 15: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 214, // 16: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 191, // 12: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 192, // 13: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 193, // 14: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 216, // 15: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 216, // 16: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct 24, // 17: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 0, // 18: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 192, // 19: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 194, // 19: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry 19, // 20: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 193, // 21: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 194, // 22: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 195, // 21: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 196, // 22: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry 18, // 23: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox 18, // 24: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 215, // 25: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 217, // 25: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider 18, // 26: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 18, // 27: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 48, // 28: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 212, // 29: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 47, // 30: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 195, // 31: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 52, // 32: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 53, // 33: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 54, // 34: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 138, // 35: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 139, // 36: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 56, // 37: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 51, // 38: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 59, // 39: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 212, // 40: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 50, // 28: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 214, // 29: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 49, // 30: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 197, // 31: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 54, // 32: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 55, // 33: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 56, // 34: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 140, // 35: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 141, // 36: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 58, // 37: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 53, // 38: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 61, // 39: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 214, // 40: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 18, // 41: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 63, // 42: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 65, // 42: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine 25, // 43: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 64, // 44: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 149, // 45: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 196, // 46: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 215, // 47: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 215, // 48: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 197, // 49: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - 215, // 50: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 215, // 51: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 93, // 52: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 76, // 53: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 81, // 54: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 77, // 55: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 66, // 44: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 151, // 45: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 198, // 46: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 217, // 47: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 217, // 48: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 199, // 49: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + 217, // 50: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 217, // 51: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 95, // 52: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 78, // 53: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 83, // 54: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 79, // 55: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant 1, // 56: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 79, // 57: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 80, // 58: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 81, // 57: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 82, // 58: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput 1, // 59: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 212, // 60: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 214, // 60: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 1, // 61: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 198, // 62: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - 199, // 63: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - 82, // 64: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 200, // 62: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + 201, // 63: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + 84, // 64: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus 1, // 65: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 200, // 66: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 82, // 67: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 82, // 68: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 202, // 66: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 84, // 67: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 84, // 68: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus 2, // 69: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 78, // 70: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 216, // 71: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 217, // 72: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 83, // 73: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 201, // 74: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 212, // 75: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 93, // 76: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile - 93, // 77: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 93, // 78: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 74, // 79: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 75, // 80: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 93, // 81: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 74, // 82: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 75, // 83: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 93, // 84: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 74, // 85: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 75, // 86: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 107, // 87: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 202, // 88: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 203, // 89: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - 204, // 90: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 205, // 91: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 213, // 92: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 218, // 93: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 111, // 94: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 206, // 95: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 112, // 96: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 113, // 97: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 114, // 98: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 115, // 99: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 116, // 100: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 117, // 101: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 219, // 102: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 220, // 103: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 221, // 104: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 207, // 105: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 125, // 106: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 125, // 107: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 80, // 70: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 218, // 71: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 219, // 72: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 85, // 73: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 203, // 74: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 214, // 75: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 95, // 76: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile + 95, // 77: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 95, // 78: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 76, // 79: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 77, // 80: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 95, // 81: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 76, // 82: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 77, // 83: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 95, // 84: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 76, // 85: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 77, // 86: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 109, // 87: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 204, // 88: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 205, // 89: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + 206, // 90: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 207, // 91: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 215, // 92: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 220, // 93: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 113, // 94: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 208, // 95: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 114, // 96: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 115, // 97: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 116, // 98: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 117, // 99: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 118, // 100: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 119, // 101: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 221, // 102: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 222, // 103: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 223, // 104: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 209, // 105: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 127, // 106: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 127, // 107: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision 3, // 108: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus 3, // 109: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 213, // 110: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 208, // 111: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 63, // 112: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 63, // 113: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 132, // 114: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 135, // 115: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 142, // 116: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 143, // 117: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 133, // 118: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 134, // 119: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 136, // 120: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 137, // 121: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 143, // 122: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 138, // 123: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 139, // 124: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 140, // 125: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 144, // 126: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 146, // 127: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 219, // 128: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 145, // 129: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 148, // 130: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 147, // 131: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 148, // 132: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 219, // 133: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 167, // 134: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 213, // 135: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 209, // 136: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry - 219, // 137: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 210, // 138: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry - 211, // 139: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 222, // 140: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 222, // 141: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 222, // 142: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 212, // 143: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 215, // 110: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 210, // 111: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 65, // 112: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 65, // 113: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 134, // 114: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 137, // 115: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 144, // 116: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 145, // 117: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 135, // 118: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 136, // 119: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 138, // 120: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 139, // 121: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 145, // 122: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 140, // 123: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 141, // 124: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 142, // 125: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 146, // 126: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 148, // 127: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 221, // 128: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 147, // 129: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 150, // 130: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 149, // 131: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 150, // 132: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 221, // 133: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 169, // 134: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 215, // 135: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 211, // 136: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry + 221, // 137: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 212, // 138: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry + 213, // 139: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 224, // 140: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 224, // 141: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 224, // 142: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 214, // 143: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 5, // 144: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole 5, // 145: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 181, // 146: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 181, // 147: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 78, // 148: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 108, // 149: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 183, // 146: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 183, // 147: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 80, // 148: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 110, // 149: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding 10, // 150: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest 12, // 151: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest 14, // 152: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest @@ -14423,126 +14557,130 @@ var file_openshell_proto_depIdxs = []int32{ 30, // 157: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest 31, // 158: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest 32, // 159: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 39, // 160: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 41, // 161: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 42, // 162: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 43, // 163: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 45, // 164: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 49, // 165: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 51, // 166: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 57, // 167: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 58, // 168: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 65, // 169: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 66, // 170: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 67, // 171: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 72, // 172: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 73, // 173: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 97, // 174: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 99, // 175: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 101, // 176: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 68, // 177: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 85, // 178: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 87, // 179: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 89, // 180: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 91, // 181: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 69, // 182: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 104, // 183: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 223, // 184: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 224, // 185: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 110, // 186: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 119, // 187: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 121, // 188: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 123, // 189: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 106, // 190: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 126, // 191: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 127, // 192: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 130, // 193: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 141, // 194: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 61, // 195: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 150, // 196: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 152, // 197: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 154, // 198: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 156, // 199: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 158, // 200: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 160, // 201: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 162, // 202: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 164, // 203: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 166, // 204: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 6, // 205: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 8, // 206: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 173, // 207: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 175, // 208: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 177, // 209: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 179, // 210: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 182, // 211: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 184, // 212: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 186, // 213: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 11, // 214: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 13, // 215: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 15, // 216: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 33, // 217: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 33, // 218: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 34, // 219: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 35, // 220: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 36, // 221: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 37, // 222: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 38, // 223: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 40, // 224: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 48, // 225: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 48, // 226: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 44, // 227: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 46, // 228: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 50, // 229: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 55, // 230: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 57, // 231: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 55, // 232: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 70, // 233: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 70, // 234: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 71, // 235: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 96, // 236: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 95, // 237: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 98, // 238: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 100, // 239: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 102, // 240: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 70, // 241: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 86, // 242: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 88, // 243: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 90, // 244: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 92, // 245: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 103, // 246: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 105, // 247: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 225, // 248: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 226, // 249: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 118, // 250: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 120, // 251: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 122, // 252: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 124, // 253: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 109, // 254: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 129, // 255: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 128, // 256: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 131, // 257: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 141, // 258: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 62, // 259: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 151, // 260: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 153, // 261: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 155, // 262: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 157, // 263: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 159, // 264: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 161, // 265: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 163, // 266: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 165, // 267: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 168, // 268: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 7, // 269: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 9, // 270: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 174, // 271: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 176, // 272: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 178, // 273: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 180, // 274: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 183, // 275: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 185, // 276: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 187, // 277: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 214, // [214:278] is the sub-list for method output_type - 150, // [150:214] is the sub-list for method input_type + 33, // 160: openshell.v1.OpenShell.SuspendSandbox:input_type -> openshell.v1.SuspendSandboxRequest + 34, // 161: openshell.v1.OpenShell.ResumeSandbox:input_type -> openshell.v1.ResumeSandboxRequest + 41, // 162: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 43, // 163: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 44, // 164: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 45, // 165: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 47, // 166: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 51, // 167: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 53, // 168: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 59, // 169: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 60, // 170: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 67, // 171: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 68, // 172: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 69, // 173: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 74, // 174: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 75, // 175: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 99, // 176: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 101, // 177: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 103, // 178: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 70, // 179: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 87, // 180: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 89, // 181: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 91, // 182: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 93, // 183: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 71, // 184: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 106, // 185: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 225, // 186: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 226, // 187: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 112, // 188: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 121, // 189: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 123, // 190: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 125, // 191: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 108, // 192: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 128, // 193: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 129, // 194: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 132, // 195: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 143, // 196: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 63, // 197: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 152, // 198: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 154, // 199: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 156, // 200: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 158, // 201: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 160, // 202: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 162, // 203: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 164, // 204: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 166, // 205: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 168, // 206: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 6, // 207: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 8, // 208: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 175, // 209: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 177, // 210: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 179, // 211: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 181, // 212: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 184, // 213: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 186, // 214: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 188, // 215: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 11, // 216: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 13, // 217: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 15, // 218: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 35, // 219: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 35, // 220: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 36, // 221: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 37, // 222: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 38, // 223: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 39, // 224: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 40, // 225: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 35, // 226: openshell.v1.OpenShell.SuspendSandbox:output_type -> openshell.v1.SandboxResponse + 35, // 227: openshell.v1.OpenShell.ResumeSandbox:output_type -> openshell.v1.SandboxResponse + 42, // 228: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 50, // 229: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 50, // 230: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 46, // 231: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 48, // 232: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 52, // 233: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 57, // 234: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 59, // 235: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 57, // 236: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 72, // 237: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 72, // 238: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 73, // 239: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 98, // 240: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 97, // 241: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 100, // 242: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 102, // 243: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 104, // 244: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 72, // 245: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 88, // 246: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 90, // 247: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 92, // 248: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 94, // 249: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 105, // 250: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 107, // 251: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 227, // 252: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 228, // 253: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 120, // 254: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 122, // 255: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 124, // 256: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 126, // 257: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 111, // 258: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 131, // 259: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 130, // 260: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 133, // 261: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 143, // 262: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 64, // 263: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 153, // 264: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 155, // 265: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 157, // 266: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 159, // 267: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 161, // 268: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 163, // 269: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 165, // 270: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 167, // 271: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 170, // 272: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 7, // 273: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 9, // 274: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 176, // 275: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 178, // 276: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 180, // 277: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 182, // 278: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 185, // 279: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 187, // 280: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 189, // 281: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 216, // [216:282] is the sub-list for method output_type + 150, // [150:216] is the sub-list for method input_type 150, // [150:150] is the sub-list for extension type_name 150, // [150:150] is the sub-list for extension extendee 0, // [0:150] is the sub-list for field type_name @@ -14555,33 +14693,33 @@ func file_openshell_proto_init() { } file_openshell_proto_msgTypes[15].OneofWrappers = []any{} file_openshell_proto_msgTypes[16].OneofWrappers = []any{} - file_openshell_proto_msgTypes[49].OneofWrappers = []any{ + file_openshell_proto_msgTypes[51].OneofWrappers = []any{ (*ExecSandboxEvent_Stdout)(nil), (*ExecSandboxEvent_Stderr)(nil), (*ExecSandboxEvent_Exit)(nil), } - file_openshell_proto_msgTypes[50].OneofWrappers = []any{ + file_openshell_proto_msgTypes[52].OneofWrappers = []any{ (*TcpForwardInit_Ssh)(nil), (*TcpForwardInit_Tcp)(nil), } - file_openshell_proto_msgTypes[51].OneofWrappers = []any{ + file_openshell_proto_msgTypes[53].OneofWrappers = []any{ (*TcpForwardFrame_Init)(nil), (*TcpForwardFrame_Data)(nil), } - file_openshell_proto_msgTypes[52].OneofWrappers = []any{ + file_openshell_proto_msgTypes[54].OneofWrappers = []any{ (*ExecSandboxInput_Start)(nil), (*ExecSandboxInput_Stdin)(nil), (*ExecSandboxInput_Resize)(nil), } - file_openshell_proto_msgTypes[56].OneofWrappers = []any{ + file_openshell_proto_msgTypes[58].OneofWrappers = []any{ (*SandboxStreamEvent_Sandbox)(nil), (*SandboxStreamEvent_Log)(nil), (*SandboxStreamEvent_Event)(nil), (*SandboxStreamEvent_Warning)(nil), (*SandboxStreamEvent_DraftPolicyUpdate)(nil), } - file_openshell_proto_msgTypes[81].OneofWrappers = []any{} - file_openshell_proto_msgTypes[105].OneofWrappers = []any{ + file_openshell_proto_msgTypes[83].OneofWrappers = []any{} + file_openshell_proto_msgTypes[107].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -14589,36 +14727,36 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[124].OneofWrappers = []any{ + file_openshell_proto_msgTypes[126].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[125].OneofWrappers = []any{ + file_openshell_proto_msgTypes[127].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), (*GatewayMessage_RelayOpen)(nil), (*GatewayMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[131].OneofWrappers = []any{ + file_openshell_proto_msgTypes[133].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[135].OneofWrappers = []any{ + file_openshell_proto_msgTypes[137].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[165].OneofWrappers = []any{} - file_openshell_proto_msgTypes[166].OneofWrappers = []any{} + file_openshell_proto_msgTypes[167].OneofWrappers = []any{} + file_openshell_proto_msgTypes[168].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), NumEnums: 6, - NumMessages: 206, + NumMessages: 208, NumExtensions: 0, NumServices: 1, }, diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go index 40d625a394..ef4b4c2669 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -33,6 +33,8 @@ const ( OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" + OpenShell_SuspendSandbox_FullMethodName = "/openshell.v1.OpenShell/SuspendSandbox" + OpenShell_ResumeSandbox_FullMethodName = "/openshell.v1.OpenShell/ResumeSandbox" OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" @@ -122,6 +124,10 @@ type OpenShellClient interface { DetachSandboxProvider(ctx context.Context, in *DetachSandboxProviderRequest, opts ...grpc.CallOption) (*DetachSandboxProviderResponse, error) // Delete a sandbox by name. DeleteSandbox(ctx context.Context, in *DeleteSandboxRequest, opts ...grpc.CallOption) (*DeleteSandboxResponse, error) + // Suspend a sandbox while retaining its persistent state. + SuspendSandbox(ctx context.Context, in *SuspendSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) + // Resume a previously suspended sandbox. + ResumeSandbox(ctx context.Context, in *ResumeSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) // Create a short-lived SSH session for a sandbox. CreateSshSession(ctx context.Context, in *CreateSshSessionRequest, opts ...grpc.CallOption) (*CreateSshSessionResponse, error) // Create or update a sandbox HTTP service endpoint for local routing. @@ -379,6 +385,26 @@ func (c *openShellClient) DeleteSandbox(ctx context.Context, in *DeleteSandboxRe return out, nil } +func (c *openShellClient) SuspendSandbox(ctx context.Context, in *SuspendSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SandboxResponse) + err := c.cc.Invoke(ctx, OpenShell_SuspendSandbox_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ResumeSandbox(ctx context.Context, in *ResumeSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SandboxResponse) + err := c.cc.Invoke(ctx, OpenShell_ResumeSandbox_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *openShellClient) CreateSshSession(ctx context.Context, in *CreateSshSessionRequest, opts ...grpc.CallOption) (*CreateSshSessionResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreateSshSessionResponse) @@ -985,6 +1011,10 @@ type OpenShellServer interface { DetachSandboxProvider(context.Context, *DetachSandboxProviderRequest) (*DetachSandboxProviderResponse, error) // Delete a sandbox by name. DeleteSandbox(context.Context, *DeleteSandboxRequest) (*DeleteSandboxResponse, error) + // Suspend a sandbox while retaining its persistent state. + SuspendSandbox(context.Context, *SuspendSandboxRequest) (*SandboxResponse, error) + // Resume a previously suspended sandbox. + ResumeSandbox(context.Context, *ResumeSandboxRequest) (*SandboxResponse, error) // Create a short-lived SSH session for a sandbox. CreateSshSession(context.Context, *CreateSshSessionRequest) (*CreateSshSessionResponse, error) // Create or update a sandbox HTTP service endpoint for local routing. @@ -1172,6 +1202,12 @@ func (UnimplementedOpenShellServer) DetachSandboxProvider(context.Context, *Deta func (UnimplementedOpenShellServer) DeleteSandbox(context.Context, *DeleteSandboxRequest) (*DeleteSandboxResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteSandbox not implemented") } +func (UnimplementedOpenShellServer) SuspendSandbox(context.Context, *SuspendSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SuspendSandbox not implemented") +} +func (UnimplementedOpenShellServer) ResumeSandbox(context.Context, *ResumeSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ResumeSandbox not implemented") +} func (UnimplementedOpenShellServer) CreateSshSession(context.Context, *CreateSshSessionRequest) (*CreateSshSessionResponse, error) { return nil, status.Error(codes.Unimplemented, "method CreateSshSession not implemented") } @@ -1535,6 +1571,42 @@ func _OpenShell_DeleteSandbox_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _OpenShell_SuspendSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SuspendSandboxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).SuspendSandbox(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_SuspendSandbox_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).SuspendSandbox(ctx, req.(*SuspendSandboxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ResumeSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResumeSandboxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ResumeSandbox(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ResumeSandbox_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ResumeSandbox(ctx, req.(*ResumeSandboxRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _OpenShell_CreateSshSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreateSshSessionRequest) if err := dec(in); err != nil { @@ -2485,6 +2557,14 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteSandbox", Handler: _OpenShell_DeleteSandbox_Handler, }, + { + MethodName: "SuspendSandbox", + Handler: _OpenShell_SuspendSandbox_Handler, + }, + { + MethodName: "ResumeSandbox", + Handler: _OpenShell_ResumeSandbox_Handler, + }, { MethodName: "CreateSshSession", Handler: _OpenShell_CreateSshSession_Handler, From 44512db0a0193b4c8f377ed7e2528ff00f29c3d5 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 16:13:52 -0500 Subject: [PATCH 02/15] fix(server): preserve lifecycle work after cancellation Signed-off-by: Seth Jennings --- crates/openshell-server/src/compute/mod.rs | 205 ++++++++++++++++++++- 1 file changed, 204 insertions(+), 1 deletion(-) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index b76c5bf409..bb35fe7e53 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -1025,6 +1025,45 @@ impl ComputeRuntime { self.sandbox_watch_bus.notify(&sandbox_id); drop(global_guard); + // Once the durable transition is committed, request cancellation must + // not cancel the driver operation and strand the sandbox in + // `Suspending`. Keep the lifecycle gate in an owned worker, matching + // the delete path's cancellation semantics. + let runtime = self.clone(); + let workspace = workspace.to_string(); + let request_span = tracing::Span::current(); + tokio::spawn( + async move { + runtime + .complete_sandbox_suspend( + workspace, + sandbox_id, + sandbox_name, + previous, + suspending, + lifecycle_guard, + ) + .await + } + .instrument(request_span), + ) + .await + .map_err(|err| { + Status::internal(format!( + "sandbox suspend worker terminated unexpectedly: {err}" + )) + })? + } + + async fn complete_sandbox_suspend( + &self, + workspace: String, + sandbox_id: String, + sandbox_name: String, + previous: Sandbox, + suspending: Sandbox, + lifecycle_guard: SandboxLifecycleGuard, + ) -> Result { let result = self .driver .call("driver.stop_sandbox", Some(&sandbox_id), |driver| { @@ -1066,7 +1105,7 @@ impl ComputeRuntime { "sandbox lifecycle changed while suspension completed", )); }; - self.cleanup_sandbox_ssh_sessions(&sandbox_id, workspace) + self.cleanup_sandbox_ssh_sessions(&sandbox_id, &workspace) .await .map_err(Status::internal)?; self.supervisor_sessions.disconnect(&sandbox_id); @@ -1138,6 +1177,40 @@ impl ComputeRuntime { self.sandbox_watch_bus.notify(&sandbox_id); drop(global_guard); + // The durable `Resuming` transition commits the operation. Let an + // owned worker finish it even if the initiating RPC is canceled. + let runtime = self.clone(); + let request_span = tracing::Span::current(); + tokio::spawn( + async move { + runtime + .complete_sandbox_resume( + sandbox_id, + sandbox_name, + previous, + resuming, + lifecycle_guard, + ) + .await + } + .instrument(request_span), + ) + .await + .map_err(|err| { + Status::internal(format!( + "sandbox resume worker terminated unexpectedly: {err}" + )) + })? + } + + async fn complete_sandbox_resume( + &self, + sandbox_id: String, + sandbox_name: String, + previous: Sandbox, + resuming: Sandbox, + lifecycle_guard: SandboxLifecycleGuard, + ) -> Result { let result = self .driver .call("driver.resume_sandbox", Some(&sandbox_id), |driver| { @@ -3942,7 +4015,15 @@ mod tests { delete_blocked: AtomicBool, delete_calls: AtomicUsize, delete_outcome: TestMutex, + stop_started: Notify, + stop_finished: Notify, + stop_release: Semaphore, + stop_blocked: AtomicBool, stop_calls: AtomicUsize, + resume_started: Notify, + resume_finished: Notify, + resume_release: Semaphore, + resume_blocked: AtomicBool, resume_calls: AtomicUsize, get_started: Notify, get_release: Semaphore, @@ -3962,7 +4043,15 @@ mod tests { delete_blocked: AtomicBool::new(false), delete_calls: AtomicUsize::new(0), delete_outcome: TestMutex::new(ControlledDeleteOutcome::Ok(true)), + stop_started: Notify::new(), + stop_finished: Notify::new(), + stop_release: Semaphore::new(0), + stop_blocked: AtomicBool::new(false), stop_calls: AtomicUsize::new(0), + resume_started: Notify::new(), + resume_finished: Notify::new(), + resume_release: Semaphore::new(0), + resume_blocked: AtomicBool::new(false), resume_calls: AtomicUsize::new(0), get_started: Notify::new(), get_release: Semaphore::new(0), @@ -3979,6 +4068,22 @@ mod tests { self.delete_release.add_permits(1); } + fn block_stop(&self) { + self.stop_blocked.store(true, Ordering::SeqCst); + } + + fn release_stop(&self) { + self.stop_release.add_permits(1); + } + + fn block_resume(&self) { + self.resume_blocked.store(true, Ordering::SeqCst); + } + + fn release_resume(&self) { + self.resume_release.add_permits(1); + } + fn block_get(&self) { self.get_blocked.store(true, Ordering::SeqCst); } @@ -4102,6 +4207,15 @@ mod tests { _request: Request, ) -> Result, Status> { self.stop_calls.fetch_add(1, Ordering::SeqCst); + self.stop_started.notify_one(); + if self.stop_blocked.load(Ordering::SeqCst) { + self.stop_release + .acquire() + .await + .expect("stop release semaphore closed") + .forget(); + } + self.stop_finished.notify_one(); Ok(tonic::Response::new(StopSandboxResponse {})) } @@ -4110,6 +4224,15 @@ mod tests { _request: Request, ) -> Result, Status> { self.resume_calls.fetch_add(1, Ordering::SeqCst); + self.resume_started.notify_one(); + if self.resume_blocked.load(Ordering::SeqCst) { + self.resume_release + .acquire() + .await + .expect("resume release semaphore closed") + .forget(); + } + self.resume_finished.notify_one(); Ok(tonic::Response::new(ResumeSandboxResponse {})) } @@ -5016,6 +5139,86 @@ mod tests { assert_eq!(driver.resume_calls(), 1, "ready resume is idempotent"); } + #[tokio::test] + async fn request_cancellation_does_not_cancel_suspend_worker() { + let driver = ControlledDriver::new(); + driver.block_stop(); + let runtime = test_runtime(driver.clone()).await; + let sandbox = sandbox_record("sb-suspend", "sandbox-suspend", SandboxPhase::Ready); + runtime.store.put_message(&sandbox).await.unwrap(); + + let request_runtime = runtime.clone(); + let request = tokio::spawn(async move { + request_runtime + .suspend_sandbox("default", "sandbox-suspend") + .await + }); + tokio::time::timeout(Duration::from_secs(1), driver.stop_started.notified()) + .await + .expect("suspend did not reach the driver"); + + request.abort(); + assert!(request.await.unwrap_err().is_cancelled()); + driver.release_stop(); + tokio::time::timeout(Duration::from_secs(1), driver.stop_finished.notified()) + .await + .expect("detached suspend worker did not finish the driver call"); + + tokio::time::timeout(Duration::from_secs(1), async { + loop { + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + if stored.phase() == SandboxPhase::Suspended as i32 { + break; + } + tokio::task::yield_now().await; + } + }) + .await + .expect("detached suspend worker did not persist Suspended"); + assert_eq!(driver.stop_calls(), 1); + } + + #[tokio::test] + async fn request_cancellation_does_not_cancel_resume_worker() { + let driver = ControlledDriver::new(); + driver.block_resume(); + let runtime = test_runtime(driver.clone()).await; + let sandbox = sandbox_record("sb-resume", "sandbox-resume", SandboxPhase::Suspended); + runtime.store.put_message(&sandbox).await.unwrap(); + + let request_runtime = runtime.clone(); + let request = tokio::spawn(async move { + request_runtime + .resume_sandbox("default", "sandbox-resume") + .await + }); + tokio::time::timeout(Duration::from_secs(1), driver.resume_started.notified()) + .await + .expect("resume did not reach the driver"); + + request.abort(); + assert!(request.await.unwrap_err().is_cancelled()); + driver.release_resume(); + tokio::time::timeout(Duration::from_secs(1), driver.resume_finished.notified()) + .await + .expect("detached resume worker did not finish the driver call"); + + let resuming = tokio::time::timeout( + Duration::from_secs(1), + runtime.resume_sandbox("default", sandbox.object_name()), + ) + .await + .expect("detached resume worker did not release the lifecycle gate") + .unwrap(); + assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); + assert_eq!(driver.resume_calls(), 1); + } + #[tokio::test] async fn lifecycle_operations_reject_invalid_source_phases() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; From 07b898eeb6b788d922bb76e240e2378f89dcc847 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 16:18:10 -0500 Subject: [PATCH 03/15] fix(server): reconcile ambiguous lifecycle outcomes Signed-off-by: Seth Jennings --- crates/openshell-server/src/compute/mod.rs | 178 ++++++++++++++++++++- 1 file changed, 172 insertions(+), 6 deletions(-) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index bb35fe7e53..b81e235a31 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -1080,9 +1080,9 @@ impl ComputeRuntime { }) .await; - let _global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; match result { Ok(_) => { + let _global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; let latest = self .store .get_message::(&sandbox_id) @@ -1114,7 +1114,7 @@ impl ComputeRuntime { Ok(suspended) } Err(err) => { - self.restore_lifecycle_snapshot(&suspending, &previous) + self.recover_failed_lifecycle(&lifecycle_guard, &suspending, &previous, true) .await; Err(Status::new( err.code(), @@ -1227,9 +1227,9 @@ impl ComputeRuntime { }) .await; - let _global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; match result { Ok(_) => { + let _global_guard = self.lock_global_for_lifecycle(&lifecycle_guard).await; let latest = self .store .get_message::(&sandbox_id) @@ -1239,7 +1239,8 @@ impl ComputeRuntime { Ok(latest) } Err(err) => { - self.restore_lifecycle_snapshot(&resuming, &previous).await; + self.recover_failed_lifecycle(&lifecycle_guard, &resuming, &previous, false) + .await; Err(Status::new( err.code(), format!("resume sandbox failed: {}", err.message()), @@ -1248,6 +1249,74 @@ impl ComputeRuntime { } } + /// Reconcile an ambiguous lifecycle error against the driver's observed + /// state before deciding whether the pre-operation snapshot is still true. + /// + /// A transport error can arrive after the runtime applied stop or start. + /// The driver lookup deliberately runs without the process-wide lock; the + /// exact transition resource version then fences the recovery write. + async fn recover_failed_lifecycle( + &self, + lifecycle_guard: &SandboxLifecycleGuard, + transition: &Sandbox, + previous: &Sandbox, + expected_stopped: bool, + ) { + let sandbox_id = transition.object_id(); + let sandbox_name = transition.object_name(); + let observed = self.get_driver_sandbox(sandbox_id, sandbox_name).await; + let _global_guard = self.lock_global_for_lifecycle(lifecycle_guard).await; + + match observed { + Ok(Some(snapshot)) if snapshot.id == sandbox_id && snapshot.status.is_some() => { + let backend_phase = derive_phase(snapshot.status.as_ref()); + let observed_stopped = backend_phase == SandboxPhase::Suspended + || driver_snapshot_confirms_stopped(&snapshot); + if backend_phase == SandboxPhase::Error || observed_stopped == expected_stopped { + self.reconcile_lifecycle_snapshot(transition, &snapshot) + .await; + } else { + self.restore_lifecycle_snapshot(transition, previous).await; + } + } + Ok(Some(_) | None) | Err(_) => { + // Without authoritative backend state, retain the durable + // transition rather than claiming the old running/stopped + // state. Startup recovery can safely retry the idempotent + // driver operation. + warn!( + sandbox_id, + "Could not resolve ambiguous sandbox lifecycle outcome; retaining transition" + ); + } + } + } + + async fn reconcile_lifecycle_snapshot(&self, transition: &Sandbox, snapshot: &DriverSandbox) { + let sandbox_id = transition.object_id().to_string(); + let expected_resource_version = sandbox_resource_version(transition); + let session_connected = self.supervisor_sessions.has_session(&sandbox_id); + match self + .store + .update_message_cas::(&sandbox_id, expected_resource_version, |sandbox| { + apply_driver_snapshot(sandbox, snapshot, session_connected); + }) + .await + { + Ok(reconciled) => { + self.sandbox_index.update_from_sandbox(&reconciled); + self.sandbox_watch_bus.notify(&sandbox_id); + } + Err(err) => { + debug!( + sandbox_id, + error = %err, + "Skipped lifecycle reconciliation after concurrent change" + ); + } + } + } + async fn write_lifecycle_phase( &self, sandbox: &Sandbox, @@ -4006,6 +4075,12 @@ mod tests { Error(&'static str), } + #[derive(Clone)] + enum ControlledLifecycleOutcome { + Ok, + Error(&'static str), + } + struct ControlledDriver { watch_tx: mpsc::UnboundedSender>, watch_rx: TestMutex>>>, @@ -4020,11 +4095,13 @@ mod tests { stop_release: Semaphore, stop_blocked: AtomicBool, stop_calls: AtomicUsize, + stop_outcome: TestMutex, resume_started: Notify, resume_finished: Notify, resume_release: Semaphore, resume_blocked: AtomicBool, resume_calls: AtomicUsize, + resume_outcome: TestMutex, get_started: Notify, get_release: Semaphore, get_blocked: AtomicBool, @@ -4048,11 +4125,13 @@ mod tests { stop_release: Semaphore::new(0), stop_blocked: AtomicBool::new(false), stop_calls: AtomicUsize::new(0), + stop_outcome: TestMutex::new(ControlledLifecycleOutcome::Ok), resume_started: Notify::new(), resume_finished: Notify::new(), resume_release: Semaphore::new(0), resume_blocked: AtomicBool::new(false), resume_calls: AtomicUsize::new(0), + resume_outcome: TestMutex::new(ControlledLifecycleOutcome::Ok), get_started: Notify::new(), get_release: Semaphore::new(0), get_blocked: AtomicBool::new(false), @@ -4099,6 +4178,20 @@ mod tests { .expect("delete outcome lock poisoned") = outcome; } + fn set_stop_outcome(&self, outcome: ControlledLifecycleOutcome) { + *self + .stop_outcome + .lock() + .expect("stop outcome lock poisoned") = outcome; + } + + fn set_resume_outcome(&self, outcome: ControlledLifecycleOutcome) { + *self + .resume_outcome + .lock() + .expect("resume outcome lock poisoned") = outcome; + } + fn set_get_outcome(&self, outcome: ControlledGetOutcome) { *self.get_outcome.lock().expect("get outcome lock poisoned") = outcome; } @@ -4216,7 +4309,15 @@ mod tests { .forget(); } self.stop_finished.notify_one(); - Ok(tonic::Response::new(StopSandboxResponse {})) + let outcome = self + .stop_outcome + .lock() + .expect("stop outcome lock poisoned") + .clone(); + match outcome { + ControlledLifecycleOutcome::Ok => Ok(tonic::Response::new(StopSandboxResponse {})), + ControlledLifecycleOutcome::Error(message) => Err(Status::internal(message)), + } } async fn resume_sandbox( @@ -4233,7 +4334,17 @@ mod tests { .forget(); } self.resume_finished.notify_one(); - Ok(tonic::Response::new(ResumeSandboxResponse {})) + let outcome = self + .resume_outcome + .lock() + .expect("resume outcome lock poisoned") + .clone(); + match outcome { + ControlledLifecycleOutcome::Ok => { + Ok(tonic::Response::new(ResumeSandboxResponse {})) + } + ControlledLifecycleOutcome::Error(message) => Err(Status::internal(message)), + } } async fn delete_sandbox( @@ -5219,6 +5330,61 @@ mod tests { assert_eq!(driver.resume_calls(), 1); } + #[tokio::test] + async fn failed_suspend_reconciles_backend_that_already_stopped() { + let driver = ControlledDriver::new(); + driver.set_stop_outcome(ControlledLifecycleOutcome::Error("response lost")); + let sandbox = sandbox_record("sb-suspend", "sandbox-suspend", SandboxPhase::Ready); + let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); + stopped.status = Some(make_driver_status(make_driver_condition( + "ContainerExited", + "container stopped before the response was lost", + ))); + driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new(stopped))); + let runtime = test_runtime(driver).await; + runtime.store.put_message(&sandbox).await.unwrap(); + + let err = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap_err(); + assert!(err.message().contains("response lost")); + + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + } + + #[tokio::test] + async fn failed_resume_reconciles_backend_that_already_started() { + let driver = ControlledDriver::new(); + driver.set_resume_outcome(ControlledLifecycleOutcome::Error("response lost")); + let sandbox = sandbox_record("sb-resume", "sandbox-resume", SandboxPhase::Suspended); + driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new( + ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()), + ))); + let runtime = test_runtime(driver).await; + runtime.store.put_message(&sandbox).await.unwrap(); + + let err = runtime + .resume_sandbox("default", sandbox.object_name()) + .await + .unwrap_err(); + assert!(err.message().contains("response lost")); + + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(stored.phase(), SandboxPhase::Resuming as i32); + } + #[tokio::test] async fn lifecycle_operations_reject_invalid_source_phases() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; From 4c9eb54f991b9d55806d318326ab4315af418d6d Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 16:19:59 -0500 Subject: [PATCH 04/15] fix(server): complete suspended session cleanup Signed-off-by: Seth Jennings --- crates/openshell-server/src/compute/mod.rs | 104 ++++++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index b81e235a31..26b5e04d24 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -1001,6 +1001,9 @@ impl ComputeRuntime { let phase = SandboxPhase::try_from(current.phase()).unwrap_or(SandboxPhase::Unknown); if phase == SandboxPhase::Suspended { + self.cleanup_suspended_sandbox_sessions(¤t) + .await + .map_err(Status::internal)?; return Ok(current); } if phase == SandboxPhase::Suspending { @@ -1030,13 +1033,11 @@ impl ComputeRuntime { // `Suspending`. Keep the lifecycle gate in an owned worker, matching // the delete path's cancellation semantics. let runtime = self.clone(); - let workspace = workspace.to_string(); let request_span = tracing::Span::current(); tokio::spawn( async move { runtime .complete_sandbox_suspend( - workspace, sandbox_id, sandbox_name, previous, @@ -1057,7 +1058,6 @@ impl ComputeRuntime { async fn complete_sandbox_suspend( &self, - workspace: String, sandbox_id: String, sandbox_name: String, previous: Sandbox, @@ -1105,10 +1105,9 @@ impl ComputeRuntime { "sandbox lifecycle changed while suspension completed", )); }; - self.cleanup_sandbox_ssh_sessions(&sandbox_id, &workspace) + self.cleanup_suspended_sandbox_sessions(&suspended) .await .map_err(Status::internal)?; - self.supervisor_sessions.disconnect(&sandbox_id); self.sandbox_index.update_from_sandbox(&suspended); self.sandbox_watch_bus.notify(&sandbox_id); Ok(suspended) @@ -2098,6 +2097,11 @@ impl ComputeRuntime { }; let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); match phase { + SandboxPhase::Suspended => { + if let Err(err) = self.cleanup_suspended_sandbox_sessions(&sandbox).await { + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to complete recovered sandbox session cleanup"); + } + } SandboxPhase::Suspending => { let sandbox_id = sandbox.object_id().to_string(); let sandbox_name = sandbox.object_name().to_string(); @@ -2130,6 +2134,11 @@ impl ComputeRuntime { Ok(updated) => { self.sandbox_index.update_from_sandbox(&updated); self.sandbox_watch_bus.notify(updated.object_id()); + if let Err(err) = + self.cleanup_suspended_sandbox_sessions(&updated).await + { + warn!(sandbox_id = %updated.object_id(), error = %err, "Failed to complete recovered sandbox session cleanup"); + } } Err(err) => { warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to persist recovered suspension"); @@ -2742,6 +2751,16 @@ impl ComputeRuntime { Ok(()) } + async fn cleanup_suspended_sandbox_sessions(&self, sandbox: &Sandbox) -> Result<(), String> { + // Disconnect first so a store failure cannot leave the stopped + // sandbox reachable through an existing supervisor stream. Both + // operations are idempotent and are retried for durable Suspended + // records during explicit suspend requests and startup recovery. + self.supervisor_sessions.disconnect(sandbox.object_id()); + self.cleanup_sandbox_ssh_sessions(sandbox.object_id(), sandbox.object_workspace()) + .await + } + // TODO: introduce a per-sandbox cap on service endpoints and paginate // this cleanup loop, or query by sandbox label instead of scanning the // full workspace. Without a cap the flat 1,000-record page could miss @@ -5250,6 +5269,81 @@ mod tests { assert_eq!(driver.resume_calls(), 1, "ready resume is idempotent"); } + #[tokio::test] + async fn repeated_suspend_completes_session_cleanup() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let sandbox = sandbox_record("sb-suspended", "sandbox-suspended", SandboxPhase::Suspended); + let session = ssh_session_record("stale-session", sandbox.object_id()); + runtime.store.put_message(&sandbox).await.unwrap(); + runtime.store.put_message(&session).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + + let suspended = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + + assert_eq!(suspended.phase(), SandboxPhase::Suspended as i32); + assert_eq!(driver.stop_calls(), 0); + assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(session.object_id()) + .await + .unwrap() + .is_none() + ); + } + + #[tokio::test] + async fn startup_recovery_completes_suspended_session_cleanup() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let suspended = + sandbox_record("sb-suspended", "sandbox-suspended", SandboxPhase::Suspended); + let suspending = sandbox_record( + "sb-suspending", + "sandbox-suspending", + SandboxPhase::Suspending, + ); + let suspended_session = ssh_session_record("suspended-session", suspended.object_id()); + let suspending_session = ssh_session_record("suspending-session", suspending.object_id()); + for sandbox in [&suspended, &suspending] { + runtime.store.put_message(sandbox).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + } + for session in [&suspended_session, &suspending_session] { + runtime.store.put_message(session).await.unwrap(); + } + + runtime.resume_persisted_sandboxes().await.unwrap(); + + assert_eq!(driver.stop_calls(), 1); + for (sandbox, session) in [ + (&suspended, &suspended_session), + (&suspending, &suspending_session), + ] { + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(session.object_id()) + .await + .unwrap() + .is_none() + ); + } + } + #[tokio::test] async fn request_cancellation_does_not_cancel_suspend_worker() { let driver = ControlledDriver::new(); From 1eb19ed4cafd7365765a1fd372c200b5b7528e35 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 16:22:44 -0500 Subject: [PATCH 05/15] fix(vm): preserve suspension state on resume failure Signed-off-by: Seth Jennings --- crates/openshell-driver-vm/src/driver.rs | 119 +++++++++++++++++++---- 1 file changed, 100 insertions(+), 19 deletions(-) diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index 3a0ce9768c..f9f8ef95bb 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -1167,19 +1167,21 @@ impl VmDriver { .map_err(|err| { Status::internal(format!("read sandbox resume metadata failed: {err}")) })?; - match tokio::fs::remove_file(state_dir.join(SANDBOX_SUSPENDED_FILE)).await { - Ok(()) => {} - Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} - Err(err) => { - return Err(Status::internal(format!( - "remove suspension marker failed: {err}" - ))); - } - } - self.registry.lock().await.remove(&record_id); - self.restore_persisted_sandbox(sandbox, state_dir, &tracing::Span::current()) + let suspended_record = self + .registry + .lock() + .await + .remove(&record_id) + .ok_or_else(|| Status::not_found("sandbox not found"))?; + let restored = self + .restore_persisted_sandbox(sandbox, state_dir, true, &tracing::Span::current()) .await; - if !self.registry.lock().await.contains_key(&record_id) { + if !restored { + self.registry + .lock() + .await + .entry(record_id) + .or_insert(suspended_record); return Err(Status::internal("failed to resume persisted VM sandbox")); } Ok(()) @@ -1412,24 +1414,29 @@ impl VmDriver { continue; } - self.restore_persisted_sandbox(sandbox, state_dir, &tracing::Span::current()) + self.restore_persisted_sandbox(sandbox, state_dir, false, &tracing::Span::current()) .await; } } + /// Restore a persisted sandbox and report whether the driver accepted it. + /// For explicit resume, the suspension marker is cleared only after all + /// restore preflight checks pass and the replacement registry record is + /// installed. A failed restore therefore remains durably suspended. async fn restore_persisted_sandbox( &self, sandbox: Sandbox, state_dir: PathBuf, + clear_suspension_marker: bool, reconciliation_span: &tracing::Span, - ) { + ) -> bool { let Some(image_ref) = self.resolved_sandbox_image(&sandbox) else { warn!( sandbox_id = %sandbox.id, sandbox_name = %sandbox.name, "vm driver: cannot restore persisted sandbox without image" ); - return; + return false; }; let tls_paths = match self.config.tls_paths() { Ok(paths) => paths, @@ -1440,7 +1447,7 @@ impl VmDriver { error = %err, "vm driver: cannot restore persisted sandbox TLS configuration" ); - return; + return false; } }; @@ -1452,7 +1459,7 @@ impl VmDriver { error = %err.message(), "vm driver: cannot restore persisted sandbox extension state" ); - return; + return false; } let persisted = RestoreContext { @@ -1467,14 +1474,14 @@ impl VmDriver { error = %err, "vm driver: lifecycle extension rejected persisted sandbox restore" ); - return; + return false; } let snapshot = sandbox_snapshot(&sandbox, provisioning_condition(), false); { let mut registry = self.registry.lock().await; if registry.contains_key(&sandbox.id) { - return; + return false; } registry.insert( sandbox.id.clone(), @@ -1490,6 +1497,23 @@ impl VmDriver { ); } + if clear_suspension_marker { + match tokio::fs::remove_file(state_dir.join(SANDBOX_SUSPENDED_FILE)).await { + Ok(()) => {} + Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => { + self.registry.lock().await.remove(&sandbox.id); + warn!( + sandbox_id = %sandbox.id, + state_dir = %state_dir.display(), + error = %err, + "vm driver: cannot clear suspension marker for persisted sandbox restore" + ); + return false; + } + } + } + self.publish_platform_event( sandbox.id.clone(), platform_event( @@ -1540,6 +1564,7 @@ impl VmDriver { } else { task.abort(); } + true } fn release_gpu(&self, sandbox_id: &str) { @@ -6564,6 +6589,62 @@ mod tests { let _ = std::fs::remove_dir_all(base); } + #[tokio::test] + async fn failed_resume_preserves_suspended_state() { + let temp = tempfile::tempdir().unwrap(); + let mut driver = test_driver_with_extensions(LifecycleExtensionRegistry::new()); + driver.config.state_dir = temp.path().to_path_buf(); + let sandbox = Sandbox { + id: "sandbox-suspended".to_string(), + name: "suspended".to_string(), + ..Default::default() + }; + let state_dir = temp.path().join("sandboxes").join(&sandbox.id); + create_private_dir_all(&state_dir).await.unwrap(); + write_sandbox_request(&state_dir, &sandbox).await.unwrap(); + tokio::fs::write(state_dir.join(SANDBOX_SUSPENDED_FILE), b"suspended\n") + .await + .unwrap(); + let snapshot = sandbox_snapshot(&sandbox, suspended_condition(), false); + driver.registry.lock().await.insert( + sandbox.id.clone(), + SandboxRecord { + snapshot, + state_dir: state_dir.clone(), + process: None, + provisioning_task: None, + gpu_bdf: None, + qemu_network_allocated: false, + deleting: false, + }, + ); + + let err = driver + .resume_sandbox(&sandbox.id, &sandbox.name) + .await + .expect_err("resume without an image should fail"); + + assert_eq!(err.code(), Code::Internal); + assert!( + tokio::fs::metadata(state_dir.join(SANDBOX_SUSPENDED_FILE)) + .await + .is_ok(), + "failed resume must retain its durable suspension marker" + ); + let restored = driver + .get_sandbox(&sandbox.id, &sandbox.name) + .await + .unwrap() + .expect("failed resume must retain its suspended registry record"); + let condition = restored + .status + .as_ref() + .and_then(|status| status.conditions.first()) + .expect("suspended condition"); + assert_eq!(condition.r#type, "Suspended"); + assert_eq!(condition.status, "True"); + } + #[test] fn prepare_sandbox_overlay_preserves_existing_overlay_on_resume() { let base = unique_temp_dir(); From 3a1b5f2f8e3ef6fc91d994744dbe11f4df16930a Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 16:38:22 -0500 Subject: [PATCH 06/15] fix(server): retry retained lifecycle transitions Signed-off-by: Seth Jennings --- crates/openshell-server/src/compute/mod.rs | 115 +++++++++++++++------ 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 26b5e04d24..3510cba539 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -1006,26 +1006,30 @@ impl ComputeRuntime { .map_err(Status::internal)?; return Ok(current); } - if phase == SandboxPhase::Suspending { - return Ok(current); - } if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Suspending) { return Err(Status::failed_precondition(format!( "sandbox must be Ready to suspend (current phase: {phase:?})" ))); } - let previous = current.clone(); - let suspending = self - .write_lifecycle_phase( - ¤t, - SandboxPhase::Suspending, - "Suspending", - "Sandbox suspension requested", - ) - .await?; - self.sandbox_index.update_from_sandbox(&suspending); - self.sandbox_watch_bus.notify(&sandbox_id); + let (previous, suspending) = if phase == SandboxPhase::Suspending { + // Acquiring the lifecycle gate proves that no local worker still + // owns this transition. Retry the idempotent driver operation. + (current.clone(), current) + } else { + let previous = current.clone(); + let suspending = self + .write_lifecycle_phase( + ¤t, + SandboxPhase::Suspending, + "Suspending", + "Sandbox suspension requested", + ) + .await?; + self.sandbox_index.update_from_sandbox(&suspending); + self.sandbox_watch_bus.notify(&sandbox_id); + (previous, suspending) + }; drop(global_guard); // Once the durable transition is committed, request cancellation must @@ -1154,26 +1158,30 @@ impl ComputeRuntime { if phase == SandboxPhase::Ready { return Ok(current); } - if phase == SandboxPhase::Resuming { - return Ok(current); - } if !matches!(phase, SandboxPhase::Suspended | SandboxPhase::Resuming) { return Err(Status::failed_precondition(format!( "sandbox must be Suspended to resume (current phase: {phase:?})" ))); } - let previous = current.clone(); - let resuming = self - .write_lifecycle_phase( - ¤t, - SandboxPhase::Resuming, - "Resuming", - "Sandbox resume requested", - ) - .await?; - self.sandbox_index.update_from_sandbox(&resuming); - self.sandbox_watch_bus.notify(&sandbox_id); + let (previous, resuming) = if phase == SandboxPhase::Resuming { + // Acquiring the lifecycle gate proves that no local worker still + // owns this transition. Retry the idempotent driver operation. + (current.clone(), current) + } else { + let previous = current.clone(); + let resuming = self + .write_lifecycle_phase( + ¤t, + SandboxPhase::Resuming, + "Resuming", + "Sandbox resume requested", + ) + .await?; + self.sandbox_index.update_from_sandbox(&resuming); + self.sandbox_watch_bus.notify(&sandbox_id); + (previous, resuming) + }; drop(global_guard); // The durable `Resuming` transition commits the operation. Let an @@ -5251,7 +5259,11 @@ mod tests { .await .unwrap(); assert_eq!(resuming_again.phase(), SandboxPhase::Resuming as i32); - assert_eq!(driver.resume_calls(), 1, "in-flight resume is idempotent"); + assert_eq!( + driver.resume_calls(), + 2, + "explicit retry reissues the idempotent resume" + ); register_test_supervisor_session(&runtime, sandbox.object_id()); runtime @@ -5266,7 +5278,47 @@ mod tests { .await .unwrap(); assert_eq!(ready.phase(), SandboxPhase::Ready as i32); - assert_eq!(driver.resume_calls(), 1, "ready resume is idempotent"); + assert_eq!(driver.resume_calls(), 2, "ready resume is idempotent"); + } + + #[tokio::test] + async fn retained_suspending_transition_retries_driver_operation() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let sandbox = sandbox_record( + "sb-retained-suspend", + "sandbox-retained-suspend", + SandboxPhase::Suspending, + ); + runtime.store.put_message(&sandbox).await.unwrap(); + + let suspended = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + + assert_eq!(suspended.phase(), SandboxPhase::Suspended as i32); + assert_eq!(driver.stop_calls(), 1); + } + + #[tokio::test] + async fn retained_resuming_transition_retries_driver_operation() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + let sandbox = sandbox_record( + "sb-retained-resume", + "sandbox-retained-resume", + SandboxPhase::Resuming, + ); + runtime.store.put_message(&sandbox).await.unwrap(); + + let resuming = runtime + .resume_sandbox("default", sandbox.object_name()) + .await + .unwrap(); + + assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); + assert_eq!(driver.resume_calls(), 1); } #[tokio::test] @@ -5413,6 +5465,7 @@ mod tests { .await .expect("detached resume worker did not finish the driver call"); + driver.release_resume(); let resuming = tokio::time::timeout( Duration::from_secs(1), runtime.resume_sandbox("default", sandbox.object_name()), @@ -5421,7 +5474,7 @@ mod tests { .expect("detached resume worker did not release the lifecycle gate") .unwrap(); assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); - assert_eq!(driver.resume_calls(), 1); + assert_eq!(driver.resume_calls(), 2); } #[tokio::test] From d5d1612b52093cc3a91a9f2740459ed7b35671ba Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Thu, 6 Aug 2026 16:39:56 -0500 Subject: [PATCH 07/15] fix(server): clean sessions after suspend reconciliation Signed-off-by: Seth Jennings --- crates/openshell-server/src/compute/mod.rs | 35 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 3510cba539..350abf1095 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -1280,8 +1280,18 @@ impl ComputeRuntime { let observed_stopped = backend_phase == SandboxPhase::Suspended || driver_snapshot_confirms_stopped(&snapshot); if backend_phase == SandboxPhase::Error || observed_stopped == expected_stopped { - self.reconcile_lifecycle_snapshot(transition, &snapshot) - .await; + if let Some(reconciled) = self + .reconcile_lifecycle_snapshot(transition, &snapshot) + .await + && reconciled.phase() == SandboxPhase::Suspended as i32 + && let Err(err) = self.cleanup_suspended_sandbox_sessions(&reconciled).await + { + warn!( + sandbox_id, + error = %err, + "Failed to clean up sessions after reconciling suspended sandbox" + ); + } } else { self.restore_lifecycle_snapshot(transition, previous).await; } @@ -1299,7 +1309,11 @@ impl ComputeRuntime { } } - async fn reconcile_lifecycle_snapshot(&self, transition: &Sandbox, snapshot: &DriverSandbox) { + async fn reconcile_lifecycle_snapshot( + &self, + transition: &Sandbox, + snapshot: &DriverSandbox, + ) -> Option { let sandbox_id = transition.object_id().to_string(); let expected_resource_version = sandbox_resource_version(transition); let session_connected = self.supervisor_sessions.has_session(&sandbox_id); @@ -1313,6 +1327,7 @@ impl ComputeRuntime { Ok(reconciled) => { self.sandbox_index.update_from_sandbox(&reconciled); self.sandbox_watch_bus.notify(&sandbox_id); + Some(reconciled) } Err(err) => { debug!( @@ -1320,6 +1335,7 @@ impl ComputeRuntime { error = %err, "Skipped lifecycle reconciliation after concurrent change" ); + None } } } @@ -5490,6 +5506,9 @@ mod tests { driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new(stopped))); let runtime = test_runtime(driver).await; runtime.store.put_message(&sandbox).await.unwrap(); + let session = ssh_session_record("lost-response-session", sandbox.object_id()); + runtime.store.put_message(&session).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); let err = runtime .suspend_sandbox("default", sandbox.object_name()) @@ -5504,6 +5523,16 @@ mod tests { .unwrap() .unwrap(); assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(session.object_id()) + .await + .unwrap() + .is_none(), + "reconciled suspension revokes ephemeral SSH sessions" + ); } #[tokio::test] From 7e4e2f14589c03fed6ce24ab8a696c66c763c551 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Fri, 7 Aug 2026 10:30:33 -0500 Subject: [PATCH 08/15] test(sandbox): cover deleting suspended sandbox Signed-off-by: Seth Jennings --- e2e/rust/tests/sandbox_lifecycle.rs | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/e2e/rust/tests/sandbox_lifecycle.rs b/e2e/rust/tests/sandbox_lifecycle.rs index 261c741566..a7de33d656 100644 --- a/e2e/rust/tests/sandbox_lifecycle.rs +++ b/e2e/rust/tests/sandbox_lifecycle.rs @@ -186,6 +186,38 @@ async fn sandbox_suspend_resume_preserves_workspace() { sandbox.cleanup().await; } +#[tokio::test] +async fn sandbox_can_be_deleted_while_suspended() { + let mut sandbox = SandboxGuard::create(&["--", "true"]) + .await + .expect("sandbox create should succeed"); + + let suspend_output = run_sandbox_lifecycle_command("suspend", &sandbox.name).await; + assert!( + suspend_output.contains("Suspended sandbox"), + "expected suspend confirmation in:\n{suspend_output}", + ); + + let delete_output = run_sandbox_lifecycle_command("delete", &sandbox.name).await; + assert!( + delete_output.contains("Deleted sandbox"), + "expected delete confirmation in:\n{delete_output}", + ); + + if let Err(last_sandbox_list) = assert_sandbox_presence_eventually(&sandbox.name, false).await { + sandbox.cleanup().await; + panic!( + "suspended sandbox {} should be deleted without resuming after \ + {SANDBOX_PRESENCE_TIMEOUT:?}; last observed sandbox list: {last_sandbox_list:?}", + sandbox.name, + ); + } + + // Mark the guard cleaned up. Its idempotent delete is harmless now that + // the lifecycle operation above has removed the sandbox. + sandbox.cleanup().await; +} + #[tokio::test] async fn sandbox_create_keeps_sandbox_after_tty_command_by_default() { let mut cmd = openshell_tty_cmd(&["sandbox", "create", "--", "echo", "OK"]); From 5aa7ac71b5e245682e7718f5a0e5014c6258bc37 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Mon, 10 Aug 2026 12:17:37 -0500 Subject: [PATCH 09/15] fix(kubernetes): preserve progressing sandbox suspension Signed-off-by: Seth Jennings --- .../openshell-driver-kubernetes/src/driver.rs | 59 +++++++++- crates/openshell-server/src/compute/mod.rs | 101 +++++++++++++++++- 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 722943e154..ba04a9bd4d 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -89,6 +89,10 @@ impl From for openshell_core::ComputeDriverError { /// API server is unreachable or slow. const KUBE_API_TIMEOUT: Duration = Duration::from_secs(30); +/// Kubernetes defaults pod termination to 30 seconds when the pod template +/// omits `terminationGracePeriodSeconds`. +const DEFAULT_POD_TERMINATION_GRACE_PERIOD: Duration = Duration::from_secs(30); + const SANDBOX_GROUP: &str = "agents.x-k8s.io"; const SANDBOX_VERSION_V1BETA1: &str = "v1beta1"; const SANDBOX_VERSION_V1ALPHA1: &str = "v1alpha1"; @@ -930,11 +934,11 @@ impl KubernetesComputeDriver { } pub async fn stop_sandbox(&self, sandbox_id: &str) -> Result<(), String> { - let (agent_sandbox_api, kube_name) = self + let (agent_sandbox_api, kube_name, suspend_timeout) = self .patch_sandbox_operating_state(sandbox_id, false) .await?; - let deadline = tokio::time::Instant::now() + KUBE_API_TIMEOUT; + let deadline = tokio::time::Instant::now() + suspend_timeout; loop { let object = agent_sandbox_api .api @@ -947,7 +951,7 @@ impl KubernetesComputeDriver { if tokio::time::Instant::now() >= deadline { return Err(format!( "timed out after {}s waiting for Kubernetes sandbox to suspend", - KUBE_API_TIMEOUT.as_secs() + suspend_timeout.as_secs() )); } tokio::time::sleep(Duration::from_millis(250)).await; @@ -964,7 +968,7 @@ impl KubernetesComputeDriver { &self, sandbox_id: &str, running: bool, - ) -> Result<(AgentSandboxApi, String), String> { + ) -> Result<(AgentSandboxApi, String, Duration), String> { let agent_sandbox_api = self .supported_agent_sandbox_api(self.client.clone()) .await?; @@ -989,6 +993,7 @@ impl KubernetesComputeDriver { .into_iter() .next() .ok_or_else(|| "sandbox not found".to_string())?; + let suspend_timeout = kubernetes_sandbox_suspend_timeout(&object); let kube_name = object .metadata .name @@ -1022,7 +1027,7 @@ impl KubernetesComputeDriver { running, "Updated Kubernetes sandbox operating state" ); - Ok((agent_sandbox_api, kube_name)) + Ok((agent_sandbox_api, kube_name, suspend_timeout)) } pub async fn delete_sandbox(&self, sandbox_id: &str) -> Result { @@ -3241,6 +3246,22 @@ fn kubernetes_sandbox_is_suspended(obj: &DynamicObject) -> bool { == Some(0) } +fn kubernetes_sandbox_suspend_timeout(obj: &DynamicObject) -> Duration { + let termination_grace_period = obj + .data + .get("spec") + .and_then(|spec| spec.get("podTemplate")) + .and_then(|template| template.get("spec")) + .and_then(|spec| spec.get("terminationGracePeriodSeconds")) + .and_then(serde_json::Value::as_u64) + .map_or(DEFAULT_POD_TERMINATION_GRACE_PERIOD, Duration::from_secs); + + // The controller must observe the desired state, wait for the pod grace + // period and kubelet teardown, then reconcile the deleted pod into the + // Sandbox status. Keep one API timeout of headroom around that grace. + termination_grace_period.saturating_add(KUBE_API_TIMEOUT) +} + fn sandbox_operating_state_patch( api_version: &str, resource_version: &str, @@ -3345,6 +3366,34 @@ mod tests { assert!(alpha_resume["spec"].get("operatingMode").is_none()); } + #[test] + fn suspend_timeout_includes_pod_grace_period_and_reconcile_headroom() { + let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( + SANDBOX_GROUP, + SANDBOX_VERSION_V1BETA1, + SANDBOX_KIND, + )); + let mut sandbox = DynamicObject::new("sandbox", &resource); + + assert_eq!( + kubernetes_sandbox_suspend_timeout(&sandbox), + Duration::from_secs(60), + "an omitted grace period uses the Kubernetes 30-second default" + ); + + sandbox.data = serde_json::json!({ + "spec": { + "podTemplate": { + "spec": {"terminationGracePeriodSeconds": 45} + } + } + }); + assert_eq!( + kubernetes_sandbox_suspend_timeout(&sandbox), + Duration::from_secs(75) + ); + } + #[test] fn sandbox_api_version_probe_keeps_non_404_errors() { let err = kube_api_error(403, "sandboxes.agents.x-k8s.io is forbidden"); diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 350abf1095..4f528f5626 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -1279,7 +1279,17 @@ impl ComputeRuntime { let backend_phase = derive_phase(snapshot.status.as_ref()); let observed_stopped = backend_phase == SandboxPhase::Suspended || driver_snapshot_confirms_stopped(&snapshot); - if backend_phase == SandboxPhase::Error || observed_stopped == expected_stopped { + let suspension_progressing = + expected_stopped && driver_snapshot_confirms_suspending(&snapshot); + if suspension_progressing { + // The Kubernetes controller has accepted suspension and + // is waiting for its pod to terminate. Preserve the + // durable transition so a later watch event can complete + // it instead of claiming the sandbox is running again. + debug!(sandbox_id, "Sandbox suspension is still progressing"); + } else if backend_phase == SandboxPhase::Error + || observed_stopped == expected_stopped + { if let Some(reconciled) = self .reconcile_lifecycle_snapshot(transition, &snapshot) .await @@ -2605,6 +2615,9 @@ impl ComputeRuntime { self.sandbox_index.update_from_sandbox(&sandbox); self.sandbox_watch_bus.notify(sandbox.object_id()); + if sandbox.phase() == SandboxPhase::Suspended as i32 { + self.cleanup_suspended_sandbox_sessions(&sandbox).await?; + } Ok(()) } @@ -3432,6 +3445,9 @@ fn apply_driver_snapshot(sandbox: &mut Sandbox, incoming: &DriverSandbox, sessio { SandboxPhase::Suspended } + SandboxPhase::Suspending if driver_snapshot_confirms_suspending(incoming) => { + SandboxPhase::Suspending + } SandboxPhase::Suspending if phase != SandboxPhase::Error => SandboxPhase::Suspending, SandboxPhase::Suspended => SandboxPhase::Suspended, SandboxPhase::Resuming if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Error) => { @@ -3499,6 +3515,19 @@ fn driver_snapshot_confirms_stopped(incoming: &DriverSandbox) -> bool { }) } +fn driver_snapshot_confirms_suspending(incoming: &DriverSandbox) -> bool { + incoming.status.as_ref().is_some_and(|status| { + status.conditions.iter().any(|condition| { + condition.r#type.eq_ignore_ascii_case("Suspended") + && condition.status.eq_ignore_ascii_case("false") + && matches!( + condition.reason.to_ascii_lowercase().as_str(), + "podterminating" | "podnotterminated" + ) + }) + }) +} + fn ensure_supervisor_ready_status(status: &mut Option, sandbox_name: &str) { upsert_ready_condition( status, @@ -5535,6 +5564,76 @@ mod tests { ); } + #[tokio::test] + async fn failed_suspend_retains_progress_and_watcher_completes_cleanup() { + let driver = ControlledDriver::new(); + driver.set_stop_outcome(ControlledLifecycleOutcome::Error("suspend timed out")); + let sandbox = sandbox_record( + "sb-suspend-progressing", + "sandbox-suspend-progressing", + SandboxPhase::Ready, + ); + let mut progressing = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); + progressing.status = Some(DriverSandboxStatus { + sandbox_name: sandbox.object_name().to_string(), + instance_id: format!("{}-pod", sandbox.object_name()), + conditions: vec![ + DriverCondition { + r#type: "Suspended".to_string(), + status: "False".to_string(), + reason: "PodTerminating".to_string(), + message: "Pod is terminating. Sandbox is suspending".to_string(), + last_transition_time: String::new(), + }, + make_driver_condition("SandboxSuspended", "Sandbox is suspending"), + ], + ..Default::default() + }); + driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new(progressing.clone()))); + let runtime = test_runtime(driver).await; + runtime.store.put_message(&sandbox).await.unwrap(); + let session = ssh_session_record("progressing-session", sandbox.object_id()); + runtime.store.put_message(&session).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + + let err = runtime + .suspend_sandbox("default", sandbox.object_name()) + .await + .unwrap_err(); + assert!(err.message().contains("suspend timed out")); + + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(stored.phase(), SandboxPhase::Suspending as i32); + assert!(runtime.supervisor_sessions.has_session(sandbox.object_id())); + + progressing.status.as_mut().unwrap().conditions[0].status = "True".to_string(); + progressing.status.as_mut().unwrap().conditions[0].reason = "PodTerminated".to_string(); + runtime.apply_sandbox_update(progressing).await.unwrap(); + + let stored = runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(session.object_id()) + .await + .unwrap() + .is_none(), + "watcher-driven suspension revokes ephemeral SSH sessions" + ); + } + #[tokio::test] async fn failed_resume_reconciles_backend_that_already_started() { let driver = ControlledDriver::new(); From f0b7bf18d0bae8b1f8bb9e688778901e5a345a94 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Mon, 10 Aug 2026 12:22:01 -0500 Subject: [PATCH 10/15] fix(kubernetes): bound suspend status polling Signed-off-by: Seth Jennings --- .../openshell-driver-kubernetes/src/driver.rs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index ba04a9bd4d..02b28ea67e 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -92,6 +92,8 @@ const KUBE_API_TIMEOUT: Duration = Duration::from_secs(30); /// Kubernetes defaults pod termination to 30 seconds when the pod template /// omits `terminationGracePeriodSeconds`. const DEFAULT_POD_TERMINATION_GRACE_PERIOD: Duration = Duration::from_secs(30); +const SUSPEND_INITIAL_POLL_INTERVAL: Duration = Duration::from_millis(250); +const SUSPEND_MAX_POLL_INTERVAL: Duration = Duration::from_secs(2); const SANDBOX_GROUP: &str = "agents.x-k8s.io"; const SANDBOX_VERSION_V1BETA1: &str = "v1beta1"; @@ -939,22 +941,40 @@ impl KubernetesComputeDriver { .await?; let deadline = tokio::time::Instant::now() + suspend_timeout; + let mut poll_interval = SUSPEND_INITIAL_POLL_INTERVAL; loop { - let object = agent_sandbox_api - .api - .get(&kube_name) + let now = tokio::time::Instant::now(); + if now >= deadline { + return Err(format!( + "timed out after {}s waiting for Kubernetes sandbox to suspend", + suspend_timeout.as_secs() + )); + } + let request_timeout = KUBE_API_TIMEOUT.min(deadline.saturating_duration_since(now)); + let object = tokio::time::timeout( + request_timeout, + agent_sandbox_api.api.get(&kube_name), + ) .await + .map_err(|_| { + format!( + "timed out after {}s waiting for Kubernetes API while checking sandbox suspension", + request_timeout.as_secs() + ) + })? .map_err(|err| err.to_string())?; if kubernetes_sandbox_is_suspended(&object) { return Ok(()); } - if tokio::time::Instant::now() >= deadline { + let now = tokio::time::Instant::now(); + if now >= deadline { return Err(format!( "timed out after {}s waiting for Kubernetes sandbox to suspend", suspend_timeout.as_secs() )); } - tokio::time::sleep(Duration::from_millis(250)).await; + tokio::time::sleep(poll_interval.min(deadline.saturating_duration_since(now))).await; + poll_interval = next_suspend_poll_interval(poll_interval); } } @@ -3262,6 +3282,10 @@ fn kubernetes_sandbox_suspend_timeout(obj: &DynamicObject) -> Duration { termination_grace_period.saturating_add(KUBE_API_TIMEOUT) } +fn next_suspend_poll_interval(current: Duration) -> Duration { + current.saturating_mul(2).min(SUSPEND_MAX_POLL_INTERVAL) +} + fn sandbox_operating_state_patch( api_version: &str, resource_version: &str, @@ -3394,6 +3418,22 @@ mod tests { ); } + #[test] + fn suspend_poll_interval_backs_off_to_cap() { + let mut interval = SUSPEND_INITIAL_POLL_INTERVAL; + let expected = [ + Duration::from_millis(500), + Duration::from_secs(1), + Duration::from_secs(2), + Duration::from_secs(2), + ]; + + for expected_interval in expected { + interval = next_suspend_poll_interval(interval); + assert_eq!(interval, expected_interval); + } + } + #[test] fn sandbox_api_version_probe_keeps_non_404_errors() { let err = kube_api_error(403, "sandboxes.agents.x-k8s.io is forbidden"); From 89acf889a84599c3d891ccc2cfc87fc4bb5a7d29 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Mon, 10 Aug 2026 12:25:07 -0500 Subject: [PATCH 11/15] fix(kubernetes): detect legacy sandbox suspension Signed-off-by: Seth Jennings --- crates/openshell-driver-kubernetes/README.md | 4 +- .../openshell-driver-kubernetes/src/driver.rs | 79 ++++++++++++++++--- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index 78b99eb0cb..fb79e25b1c 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -41,7 +41,9 @@ its pod. The driver sets `spec.operatingMode: Suspended` for `v1beta1` or `spec.replicas: 0` for `v1alpha1`. Resume sets `Running` or one replica for the same resource, so the replacement pod mounts the existing claim. Delete is the only lifecycle operation that removes the Sandbox resource and its owned -storage. +storage. The driver confirms suspension from the published `Suspended` +condition when available. Legacy `v1alpha1` controllers omit a zero replica +count from status, so the driver confirms that their backing pod is gone. The workspace PVC size defaults to `workspace_default_storage_size`. Set `workspace_storage_class` to pin the PVC to a specific `StorageClass`; an empty diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 02b28ea67e..e66b78bbb1 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -11,7 +11,8 @@ use crate::config::{ }; use futures::{Stream, StreamExt, TryStreamExt}; use k8s_openapi::api::core::v1::{ - Event as KubeEventObj, Namespace, Node, PersistentVolumeClaimVolumeSource, Volume, VolumeMount, + Event as KubeEventObj, Namespace, Node, PersistentVolumeClaimVolumeSource, Pod, Volume, + VolumeMount, }; use kube::api::{ Api, ApiResource, DeleteParams, ListParams, Patch, PatchParams, PostParams, Preconditions, @@ -100,6 +101,7 @@ const SANDBOX_VERSION_V1BETA1: &str = "v1beta1"; const SANDBOX_VERSION_V1ALPHA1: &str = "v1alpha1"; const SANDBOX_VERSIONS: &[&str] = &[SANDBOX_VERSION_V1BETA1, SANDBOX_VERSION_V1ALPHA1]; pub const SANDBOX_KIND: &str = "Sandbox"; +const SANDBOX_POD_NAME_ANNOTATION: &str = "agents.x-k8s.io/pod-name"; const GPU_RESOURCE_NAME: &str = "nvidia.com/gpu"; const SPIFFE_WORKLOAD_API_VOLUME_NAME: &str = "spiffe-workload-api"; @@ -936,9 +938,11 @@ impl KubernetesComputeDriver { } pub async fn stop_sandbox(&self, sandbox_id: &str) -> Result<(), String> { - let (agent_sandbox_api, kube_name, suspend_timeout) = self + let (agent_sandbox_api, kube_name, pod_name, suspend_timeout) = self .patch_sandbox_operating_state(sandbox_id, false) .await?; + let legacy_pod_api = (agent_sandbox_api.resource.version == SANDBOX_VERSION_V1ALPHA1) + .then(|| Api::::namespaced(self.client.clone(), &self.config.namespace)); let deadline = tokio::time::Instant::now() + suspend_timeout; let mut poll_interval = SUSPEND_INITIAL_POLL_INTERVAL; @@ -963,7 +967,12 @@ impl KubernetesComputeDriver { ) })? .map_err(|err| err.to_string())?; - if kubernetes_sandbox_is_suspended(&object) { + if kubernetes_sandbox_has_suspended_condition(&object) { + return Ok(()); + } + if let Some(pod_api) = legacy_pod_api.as_ref() + && kubernetes_sandbox_pod_is_gone(pod_api, &pod_name, deadline).await? + { return Ok(()); } let now = tokio::time::Instant::now(); @@ -988,7 +997,7 @@ impl KubernetesComputeDriver { &self, sandbox_id: &str, running: bool, - ) -> Result<(AgentSandboxApi, String, Duration), String> { + ) -> Result<(AgentSandboxApi, String, String, Duration), String> { let agent_sandbox_api = self .supported_agent_sandbox_api(self.client.clone()) .await?; @@ -1018,6 +1027,13 @@ impl KubernetesComputeDriver { .metadata .name .ok_or_else(|| "sandbox resource has no name".to_string())?; + let pod_name = object + .metadata + .annotations + .as_ref() + .and_then(|annotations| annotations.get(SANDBOX_POD_NAME_ANNOTATION)) + .cloned() + .unwrap_or_else(|| kube_name.clone()); let resource_version = object.metadata.resource_version.unwrap_or_default(); let desired = sandbox_operating_state_patch( &agent_sandbox_api.resource.version, @@ -1047,7 +1063,7 @@ impl KubernetesComputeDriver { running, "Updated Kubernetes sandbox operating state" ); - Ok((agent_sandbox_api, kube_name, suspend_timeout)) + Ok((agent_sandbox_api, kube_name, pod_name, suspend_timeout)) } pub async fn delete_sandbox(&self, sandbox_id: &str) -> Result { @@ -3244,7 +3260,7 @@ fn status_from_object(obj: &DynamicObject) -> Option { }) } -fn kubernetes_sandbox_is_suspended(obj: &DynamicObject) -> bool { +fn kubernetes_sandbox_has_suspended_condition(obj: &DynamicObject) -> bool { obj.data .get("status") .and_then(|status| status.get("conditions")) @@ -3258,12 +3274,28 @@ fn kubernetes_sandbox_is_suspended(obj: &DynamicObject) -> bool { .is_some_and(|status| status.eq_ignore_ascii_case("true")) }) }) - || obj - .data - .get("status") - .and_then(|status| status.get("replicas")) - .and_then(serde_json::Value::as_i64) - == Some(0) +} + +async fn kubernetes_sandbox_pod_is_gone( + pod_api: &Api, + pod_name: &str, + deadline: tokio::time::Instant, +) -> Result { + let request_timeout = + KUBE_API_TIMEOUT.min(deadline.saturating_duration_since(tokio::time::Instant::now())); + if request_timeout.is_zero() { + return Ok(false); + } + + match tokio::time::timeout(request_timeout, pod_api.get(pod_name)).await { + Ok(Ok(_)) => Ok(false), + Ok(Err(KubeError::Api(err))) if err.code == 404 => Ok(true), + Ok(Err(err)) => Err(err.to_string()), + Err(_) => Err(format!( + "timed out after {}s waiting for Kubernetes API while checking sandbox pod termination", + request_timeout.as_secs() + )), + } } fn kubernetes_sandbox_suspend_timeout(obj: &DynamicObject) -> Duration { @@ -3434,6 +3466,29 @@ mod tests { } } + #[test] + fn suspended_status_requires_published_condition() { + let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( + SANDBOX_GROUP, + SANDBOX_VERSION_V1ALPHA1, + SANDBOX_KIND, + )); + let mut sandbox = DynamicObject::new("sandbox", &resource); + sandbox.data = serde_json::json!({"status": {"replicas": 0}}); + + assert!( + !kubernetes_sandbox_has_suspended_condition(&sandbox), + "v1alpha1 omits a zero status replica count on the wire; it is not a usable completion signal" + ); + + sandbox.data = serde_json::json!({ + "status": { + "conditions": [{"type": "Suspended", "status": "True"}] + } + }); + assert!(kubernetes_sandbox_has_suspended_condition(&sandbox)); + } + #[test] fn sandbox_api_version_probe_keeps_non_404_errors() { let err = kube_api_error(403, "sandboxes.agents.x-k8s.io is forbidden"); From d1a008e0568d12cc5abda437ed71b47a834e220b Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Mon, 10 Aug 2026 12:26:50 -0500 Subject: [PATCH 12/15] fix(tui): render suspended sandbox phases Signed-off-by: Seth Jennings --- crates/openshell-tui/src/lib.rs | 15 +++++++++++++++ crates/openshell-tui/src/ui/sandbox_detail.rs | 6 +++--- crates/openshell-tui/src/ui/sandboxes.rs | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/openshell-tui/src/lib.rs b/crates/openshell-tui/src/lib.rs index 40adc8d068..c8caf78110 100644 --- a/crates/openshell-tui/src/lib.rs +++ b/crates/openshell-tui/src/lib.rs @@ -2687,6 +2687,9 @@ fn phase_label(phase: i32) -> String { x if x == SandboxPhase::Ready as i32 => "Ready", x if x == SandboxPhase::Error as i32 => "Error", x if x == SandboxPhase::Deleting as i32 => "Deleting", + x if x == SandboxPhase::Suspending as i32 => "Suspending", + x if x == SandboxPhase::Suspended as i32 => "Suspended", + x if x == SandboxPhase::Resuming as i32 => "Resuming", _ => "Unknown", } .to_string() @@ -2716,6 +2719,18 @@ fn format_age(epoch_ms: i64) -> String { } } +#[cfg(test)] +mod phase_label_tests { + use super::*; + + #[test] + fn phase_label_covers_suspend_and_resume_lifecycle() { + assert_eq!(phase_label(SandboxPhase::Suspending as i32), "Suspending"); + assert_eq!(phase_label(SandboxPhase::Suspended as i32), "Suspended"); + assert_eq!(phase_label(SandboxPhase::Resuming as i32), "Resuming"); + } +} + /// Format epoch milliseconds as a human-readable UTC timestamp: `YYYY-MM-DD HH:MM`. fn format_timestamp(epoch_ms: i64) -> String { if epoch_ms <= 0 { diff --git a/crates/openshell-tui/src/ui/sandbox_detail.rs b/crates/openshell-tui/src/ui/sandbox_detail.rs index f212f21b06..e2fc605f37 100644 --- a/crates/openshell-tui/src/ui/sandbox_detail.rs +++ b/crates/openshell-tui/src/ui/sandbox_detail.rs @@ -23,15 +23,15 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect) { let phase_style = match phase { "Ready" => t.status_ok, - "Provisioning" => t.status_warn, + "Provisioning" | "Suspending" | "Resuming" => t.status_warn, "Error" => t.status_err, _ => t.muted, }; let status_indicator = match phase { "Ready" => "●", - "Provisioning" => "◐", - "Error" => "○", + "Provisioning" | "Suspending" | "Resuming" => "◐", + "Error" | "Suspended" => "○", _ => "…", }; diff --git a/crates/openshell-tui/src/ui/sandboxes.rs b/crates/openshell-tui/src/ui/sandboxes.rs index 4d239241b9..41247b988b 100644 --- a/crates/openshell-tui/src/ui/sandboxes.rs +++ b/crates/openshell-tui/src/ui/sandboxes.rs @@ -41,7 +41,7 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) { let phase_style = match phase { "Ready" => t.status_ok, - "Provisioning" => t.status_warn, + "Provisioning" | "Suspending" | "Resuming" => t.status_warn, "Error" => t.status_err, _ => t.muted, }; From c74309bad804fa67a9cba8af081a0f80e87ff939 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Tue, 11 Aug 2026 15:12:32 -0500 Subject: [PATCH 13/15] refactor(sandbox): rename suspend and resume lifecycle Signed-off-by: Seth Jennings --- .../skills/debug-openshell-cluster/SKILL.md | 2 +- .agents/skills/helm-dev-environment/SKILL.md | 2 +- .agents/skills/openshell-cli/SKILL.md | 20 +- .agents/skills/openshell-cli/cli-reference.md | 12 +- TESTING.md | 2 +- architecture/compute-runtimes.md | 24 +- crates/openshell-cli/src/commands/common.rs | 6 +- crates/openshell-cli/src/main.rs | 34 +- crates/openshell-cli/src/run.rs | 30 +- .../tests/ensure_providers_integration.rs | 8 +- .../openshell-cli/tests/mtls_integration.rs | 8 +- .../tests/provider_commands_integration.rs | 8 +- .../sandbox_create_lifecycle_integration.rs | 8 +- .../sandbox_name_fallback_integration.rs | 8 +- crates/openshell-core/src/telemetry.rs | 8 +- crates/openshell-driver-docker/README.md | 8 +- crates/openshell-driver-docker/src/lib.rs | 24 +- crates/openshell-driver-docker/src/tests.rs | 10 +- crates/openshell-driver-kubernetes/README.md | 6 +- .../openshell-driver-kubernetes/src/driver.rs | 86 +-- .../openshell-driver-kubernetes/src/grpc.rs | 12 +- crates/openshell-driver-podman/README.md | 6 +- crates/openshell-driver-podman/src/driver.rs | 12 +- crates/openshell-driver-podman/src/grpc.rs | 12 +- crates/openshell-driver-vm/README.md | 8 +- crates/openshell-driver-vm/src/driver.rs | 95 ++- crates/openshell-driver-vm/src/lifecycle.rs | 4 +- .../openshell-driver-vm/src/otel_tracing.rs | 4 +- crates/openshell-sdk/src/client.rs | 32 +- crates/openshell-sdk/src/raw.rs | 6 +- crates/openshell-sdk/src/types.rs | 12 +- crates/openshell-sdk/tests/client_mock.rs | 44 +- .../openshell-server/src/auth/method_authz.rs | 4 +- .../src/auth/sandbox_methods.rs | 8 +- crates/openshell-server/src/compute/mod.rs | 638 +++++++++--------- crates/openshell-server/src/grpc/mod.rs | 22 +- crates/openshell-server/src/grpc/sandbox.rs | 48 +- crates/openshell-server/src/lib.rs | 18 +- .../src/supervisor_session.rs | 2 +- crates/openshell-server/src/test_support.rs | 14 +- crates/openshell-server/tests/common/mod.rs | 8 +- .../tests/supervisor_relay_integration.rs | 8 +- crates/openshell-tui/src/lib.rs | 14 +- crates/openshell-tui/src/ui/sandbox_detail.rs | 6 +- crates/openshell-tui/src/ui/sandboxes.rs | 2 +- docs/reference/sandbox-compute-drivers.mdx | 24 +- docs/sandboxes/manage-sandboxes.mdx | 22 +- e2e/rust/Cargo.toml | 12 +- e2e/rust/e2e-vm.sh | 2 +- .../{gateway_resume.rs => gateway_start.rs} | 20 +- ...eway_resume.rs => podman_gateway_start.rs} | 20 +- e2e/rust/tests/sandbox_lifecycle.rs | 40 +- ..._gateway_resume.rs => vm_gateway_start.rs} | 18 +- proto/compute_driver.proto | 8 +- proto/openshell.proto | 22 +- python/openshell/sandbox.py | 28 +- python/openshell/sandbox_test.py | 44 +- rfc/0011-multi-player-design/README.md | 10 +- .../v1/internal/converter/sandbox.go | 24 +- .../v1/internal/converter/sandbox_test.go | 12 +- sdk/go/openshell/v1/sandbox.go | 6 +- sdk/go/openshell/v1/sandbox_client.go | 12 +- sdk/go/openshell/v1/sandbox_client_test.go | 26 +- sdk/go/openshell/v1/types.go | 6 +- sdk/go/openshell/v1/types/types.go | 6 +- sdk/go/proto/openshellv1/openshell.pb.go | 102 +-- sdk/go/proto/openshellv1/openshell_grpc.pb.go | 64 +- 67 files changed, 928 insertions(+), 953 deletions(-) rename e2e/rust/tests/{gateway_resume.rs => gateway_start.rs} (90%) rename e2e/rust/tests/{podman_gateway_resume.rs => podman_gateway_start.rs} (77%) rename e2e/rust/tests/{vm_gateway_resume.rs => vm_gateway_start.rs} (79%) diff --git a/.agents/skills/debug-openshell-cluster/SKILL.md b/.agents/skills/debug-openshell-cluster/SKILL.md index 4e4678a1fc..45e95234b1 100644 --- a/.agents/skills/debug-openshell-cluster/SKILL.md +++ b/.agents/skills/debug-openshell-cluster/SKILL.md @@ -464,7 +464,7 @@ openshell logs | Supervisor enters policy quarantine | A runtime candidate failed validation while `policy_validation_failure_mode = "fail_closed"` | Sandbox OCSF config/finding events, validation rationale, active generation, `previous_policy_active` | | HTTP request returns `middleware_failed` or `middleware_denied` | Selected stage failed or explicitly denied the admitted request | Sandbox OCSF logs; policy-local middleware config; service availability; `on_error` | | Custom compute driver is unavailable | Driver process/socket missing, inaccessible, or configured with a reserved/mismatched name | Socket ownership/mode, driver service logs, gateway `GetCapabilities` logs | -| Sandbox remains `Suspending` or `Resuming` | Driver stop/start failed, retained resource is missing, or the resumed supervisor has not reconnected | Gateway and driver logs; `docker inspect`, `podman inspect`, Agent Sandbox status/PVC, or VM state marker and launcher process | +| Sandbox remains `Stopping` or `Starting` | Driver stop/start failed, retained resource is missing, or a fresh supervisor has not connected | Gateway and driver logs; `docker inspect`, `podman inspect`, Agent Sandbox status/PVC, or VM state marker and launcher process | | Image pull failure | Gateway or sandbox image cannot be pulled | Runtime events and image pull credentials | | `K8s namespace not ready` with `envoy-gateway-openshell.yaml: the server could not find the requested resource` | Optional Gateway API manifest was applied without Envoy Gateway CRDs, or k3s Helm controller startup exceeded the namespace wait | Apply `deploy/kube/manifests/envoy-gateway-openshell.yaml` manually only after Envoy Gateway is installed and `grpcRoute` is enabled | | HTTPS ingress (`grpcRoute.gateway.listener.protocol=HTTPS`) connection resets or TLS handshake hangs | Envoy terminates TLS but the gateway pod still expects TLS, so the plaintext backend hop fails | Set `server.disableTls=true` so Envoy forwards plaintext to the pod; verify the listener `certificateRefs` Secret exists in the release namespace and `openshell status` over `https://` | diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index adb40a9575..06f0f5e0d0 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -220,7 +220,7 @@ ServiceAccount bootstrap and gateway-minted sandbox JWT path. --- -## Cluster Lifecycle (suspend/resume) +## Cluster Lifecycle (stop/start) Stop the cluster without losing state (faster than delete/recreate): ```bash diff --git a/.agents/skills/openshell-cli/SKILL.md b/.agents/skills/openshell-cli/SKILL.md index ee7df3d751..b49b82c5ec 100644 --- a/.agents/skills/openshell-cli/SKILL.md +++ b/.agents/skills/openshell-cli/SKILL.md @@ -298,20 +298,20 @@ openshell sandbox delete sandbox-1 sandbox-2 sandbox-3 # Multiple at once openshell sandbox delete --all ``` -### Suspend and resume sandboxes +### Stop and start sandboxes -Use suspension to stop compute while retaining the sandbox and its persistent +Use stop to halt compute while retaining the sandbox and its persistent workspace: ```bash -openshell sandbox suspend [name] -openshell sandbox resume [name] +openshell sandbox stop [name] +openshell sandbox start [name] ``` -Both commands default to the last-used sandbox. Suspend stops background -forwards and waits for `Suspended`; resume waits for `Ready`. Connect, exec, +Both commands default to the last-used sandbox. Stop stops background +forwards and waits for `Stopped`; start waits for `Ready`. Connect, exec, file transfer, forwarding, and exposed services are unavailable while -suspended. Delete remains the operation that removes retained state. +stopped. Delete remains the operation that removes retained state. --- @@ -684,7 +684,7 @@ The CLI help is always authoritative. If the help output contradicts this skill, ```bash $ openshell sandbox --help -# Shows: create, get, list, suspend, resume, delete, exec, connect, upload, download, ssh-config, provider +# Shows: create, get, list, stop, start, delete, exec, connect, upload, download, ssh-config, provider $ openshell sandbox upload --help # Shows: positional arguments (name, path, dest), usage examples @@ -706,8 +706,8 @@ $ openshell sandbox upload --help | Create sandbox with GPUs | `openshell sandbox create --gpu 1` | | Create with custom policy | `openshell sandbox create --policy ./p.yaml` | | Connect to sandbox | `openshell sandbox connect ` | -| Suspend sandbox compute | `openshell sandbox suspend [name]` | -| Resume sandbox compute | `openshell sandbox resume [name]` | +| Stop sandbox compute | `openshell sandbox stop [name]` | +| Start sandbox compute | `openshell sandbox start [name]` | | Execute in sandbox | `openshell sandbox exec --name -- ` | | Stream live logs | `openshell logs --tail` | | Incremental policy update | `openshell policy update --add-endpoint host:443:read-only:rest:enforce --binary /usr/bin/curl --wait` | diff --git a/.agents/skills/openshell-cli/cli-reference.md b/.agents/skills/openshell-cli/cli-reference.md index 801cf09ea2..ec529508de 100644 --- a/.agents/skills/openshell-cli/cli-reference.md +++ b/.agents/skills/openshell-cli/cli-reference.md @@ -48,8 +48,8 @@ openshell │ ├── create [opts] [-- CMD...] │ ├── get [name] │ ├── list [opts] -│ ├── suspend [name] -│ ├── resume [name] +│ ├── stop [name] +│ ├── start [name] │ ├── delete [name]... [--all] │ ├── exec [--name ] [opts] -- CMD... │ ├── connect [name] [--editor ] @@ -252,15 +252,15 @@ Show sandbox details and the active policy. Metadata identifies sandbox or globa Delete one or more named sandboxes, or use `--all`. Deletion stops background port forwards. -### `openshell sandbox suspend [name]` +### `openshell sandbox stop [name]` Stop sandbox compute while retaining the sandbox and persistent workspace. The name defaults to the last-used sandbox. The command stops background forwards -and waits for the `Suspended` phase. +and waits for the `Stopped` phase. -### `openshell sandbox resume [name]` +### `openshell sandbox start [name]` -Restart a suspended sandbox and wait for `Ready`. The name defaults to the +Start a stopped sandbox and wait for `Ready`. The name defaults to the last-used sandbox. ### `openshell sandbox exec [OPTIONS] -- COMMAND...` diff --git a/TESTING.md b/TESTING.md index e4008143ec..6c0829060d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -148,7 +148,7 @@ lifecycle management, output parsing, and cleanup. Suites: - Common suite (`--features e2e`) - driver-neutral CLI behavior, sandbox lifecycle, sync, port forwarding, policy, and provider tests. -- Docker suite (`--features e2e-docker`) - common suite plus Docker-only coverage such as Dockerfile image builds, Docker preflight checks, and managed Docker gateway resume. +- Docker suite (`--features e2e-docker`) - common suite plus Docker-only coverage such as Dockerfile image builds, Docker preflight checks, and managed Docker gateway start. - Docker GPU suite (`--features e2e-docker-gpu`) - Docker suite plus GPU sandbox smoke coverage. - VM suite (`--features e2e-vm`) - runs e2e tests on a VM. - Kubernetes credential-driver suite (`--features e2e-kubernetes-credential-drivers`) - targeted Kubernetes Secrets and Vault provider credential storage coverage. diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index 7bf8ac5974..e4224232dd 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -1,6 +1,6 @@ # Compute Runtimes -Compute runtimes create, suspend, resume, delete, and watch sandbox workloads for the +Compute runtimes create, stop, start, delete, and watch sandbox workloads for the gateway. They do not replace sandbox policy enforcement. Every runtime starts a workload that runs the `openshell-sandbox` supervisor, and the supervisor enforces the sandbox contract locally. @@ -84,31 +84,31 @@ The gateway records driver identity and version from the startup capability response. Elevated gateway info reports that initialized driver snapshot instead of re-querying drivers on each request. -## Suspend and Resume Lifecycle +## Stop and Start Lifecycle The gateway persists lifecycle intent before mutating compute: ```text -Ready -> Suspending -> Suspended -> Resuming -> Ready +Ready -> Stopping -> Stopped -> Starting -> Ready ``` -`StopSandbox` and `ResumeSandbox` are idempotent driver operations. Suspension +`StopSandbox` and `StartSandbox` are idempotent driver operations. Stop retains the driver resource and its persistent workspace boundary while making -exec, SSH, forwarding, and exposed services unavailable. Resume reactivates the +exec, SSH, forwarding, and exposed services unavailable. Start reactivates the same resource. The gateway requires a fresh supervisor session before a -resuming sandbox returns to `Ready`; stale driver snapshots and supervisor -sessions cannot promote a `Suspended` row. +starting sandbox returns to `Ready`; stale driver snapshots and supervisor +sessions cannot promote a `Stopped` row. -Persisted `Suspending` and `Resuming` rows are retried at startup. Stable -`Suspended` rows remain stopped. Docker and Podman retain the stopped container +Persisted `Stopping` and `Starting` rows are retried at startup. Stable +`Stopped` rows remain stopped. Docker and Podman retain the stopped container and attached storage, Kubernetes retains the Sandbox CR and PVC while scaling compute to zero, and VM retains its launch request and writable overlay beside -a suspension marker. Delete remains a separate operation that removes these +a stop marker. Delete remains a separate operation that removes these resources. ## Deletion Lifecycle -Lifecycle requests use per-sandbox gates to serialize suspend, resume, and +Lifecycle requests use per-sandbox gates to serialize stop, start, and delete attempts. A delete request resolves the name once and remains bound to that stable ID. The only combined lock order is lifecycle gate, then the gateway-wide state guard; external @@ -132,7 +132,7 @@ sessions, indexes, and watch/log buses are cleaned after confirmed removal. The request acquires both locks before starting owned work, so cancellation while queued does not leave a delete armed. After that commitment point, the owned task prevents cancellation from stranding a mutation. A gateway restart -does not resume a persisted `Deleting` operation. If the backend completed the +does not start a persisted `Deleting` operation. If the backend completed the delete, reconciliation removes the row; otherwise it can remain `Deleting`. ## Runtime Summary diff --git a/crates/openshell-cli/src/commands/common.rs b/crates/openshell-cli/src/commands/common.rs index 45554417bc..6f8fe2d52b 100644 --- a/crates/openshell-cli/src/commands/common.rs +++ b/crates/openshell-cli/src/commands/common.rs @@ -59,9 +59,9 @@ pub fn phase_name(phase: i32) -> &'static str { Ok(SandboxPhase::Ready) => "Ready", Ok(SandboxPhase::Error) => "Error", Ok(SandboxPhase::Deleting) => "Deleting", - Ok(SandboxPhase::Suspending) => "Suspending", - Ok(SandboxPhase::Suspended) => "Suspended", - Ok(SandboxPhase::Resuming) => "Resuming", + Ok(SandboxPhase::Stopping) => "Stopping", + Ok(SandboxPhase::Stopped) => "Stopped", + Ok(SandboxPhase::Starting) => "Starting", Ok(SandboxPhase::Unknown) | Err(_) => "Unknown", } } diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 5058ccec00..1195a5b3de 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -1520,17 +1520,17 @@ enum SandboxCommands { all: bool, }, - /// Suspend a sandbox while preserving its workspace. + /// Stop a sandbox while preserving its workspace. #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] - Suspend { + Stop { /// Sandbox name (defaults to last-used sandbox). #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] name: Option, }, - /// Resume a suspended sandbox. + /// Start a stopped sandbox. #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] - Resume { + Start { /// Sandbox name (defaults to last-used sandbox). #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] name: Option, @@ -3148,13 +3148,13 @@ async fn main() -> Result<()> { ) .await?; } - SandboxCommands::Suspend { name } => { + SandboxCommands::Stop { name } => { let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; - run::sandbox_suspend(endpoint, &name, &cli.workspace, &tls).await?; + run::sandbox_stop(endpoint, &name, &cli.workspace, &tls).await?; } - SandboxCommands::Resume { name } => { + SandboxCommands::Start { name } => { let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; - run::sandbox_resume(endpoint, &name, &cli.workspace, &tls).await?; + run::sandbox_start(endpoint, &name, &cli.workspace, &tls).await?; } SandboxCommands::Connect { name, editor } => { let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?; @@ -4457,22 +4457,22 @@ mod tests { } #[test] - fn sandbox_suspend_and_resume_accept_optional_names() { - let suspend = Cli::try_parse_from(["openshell", "sandbox", "suspend", "demo"]) - .expect("suspend command should parse"); + fn sandbox_stop_and_start_accept_optional_names() { + let stop = Cli::try_parse_from(["openshell", "sandbox", "stop", "demo"]) + .expect("stop command should parse"); assert!(matches!( - suspend.command, + stop.command, Some(Commands::Sandbox { - command: Some(SandboxCommands::Suspend { name: Some(ref name) }), + command: Some(SandboxCommands::Stop { name: Some(ref name) }), }) if name == "demo" )); - let resume = Cli::try_parse_from(["openshell", "sandbox", "resume"]) - .expect("resume command should parse"); + let start = Cli::try_parse_from(["openshell", "sandbox", "start"]) + .expect("start command should parse"); assert!(matches!( - resume.command, + start.command, Some(Commands::Sandbox { - command: Some(SandboxCommands::Resume { name: None }), + command: Some(SandboxCommands::Start { name: None }), }) )); } diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index f76022037c..637b4b7e3e 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -48,12 +48,12 @@ use openshell_core::proto::{ ListSandboxPoliciesRequest, ListSandboxProvidersRequest, ListSandboxesRequest, ListServicesRequest, PolicySource, PolicyStatus, Provider, ProviderCredentialRefreshStatus, ProviderCredentialRefreshStrategy, ProviderProfile, ProviderProfileDiagnostic, - ProviderProfileImportItem, RejectDraftChunkRequest, ResourceRequirements, ResumeSandboxRequest, + ProviderProfileImportItem, RejectDraftChunkRequest, ResourceRequirements, RevokeSshSessionRequest, RotateProviderCredentialRequest, Sandbox, SandboxPhase, SandboxPolicy, SandboxSpec, SandboxTemplate, ServiceEndpointResponse, SetInferenceRouteRequest, SettingScope, - SuspendSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, - UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, exec_sandbox_event, - setting_value, tcp_forward_init, + StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, + UpdateConfigRequest, UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, + exec_sandbox_event, setting_value, tcp_forward_init, }; use openshell_core::settings; use openshell_core::{ObjectId, ObjectName, ObjectWorkspace}; @@ -2419,8 +2419,8 @@ pub async fn sandbox_delete( Ok(()) } -/// Suspend a sandbox while retaining its persistent workspace. -pub async fn sandbox_suspend( +/// Stop a sandbox while retaining its persistent workspace. +pub async fn sandbox_stop( server: &str, name: &str, workspace: &str, @@ -2437,7 +2437,7 @@ pub async fn sandbox_suspend( let mut client = grpc_client(server, tls).await?; let sandbox = client - .suspend_sandbox(SuspendSandboxRequest { + .stop_sandbox(StopSandboxRequest { name: name.to_string(), workspace: workspace.to_string(), }) @@ -2445,14 +2445,14 @@ pub async fn sandbox_suspend( .into_diagnostic()? .into_inner() .sandbox - .ok_or_else(|| miette!("gateway returned no sandbox after suspend"))?; - wait_for_lifecycle_phase(&mut client, sandbox, SandboxPhase::Suspended).await?; - println!("{} Suspended sandbox {name}", "✓".green().bold()); + .ok_or_else(|| miette!("gateway returned no sandbox after stop"))?; + wait_for_lifecycle_phase(&mut client, sandbox, SandboxPhase::Stopped).await?; + println!("{} Stopped sandbox {name}", "✓".green().bold()); Ok(()) } -/// Resume a suspended sandbox and wait until it is ready. -pub async fn sandbox_resume( +/// Start a stopped sandbox and wait until it is ready. +pub async fn sandbox_start( server: &str, name: &str, workspace: &str, @@ -2460,7 +2460,7 @@ pub async fn sandbox_resume( ) -> Result<()> { let mut client = grpc_client(server, tls).await?; let sandbox = client - .resume_sandbox(ResumeSandboxRequest { + .start_sandbox(StartSandboxRequest { name: name.to_string(), workspace: workspace.to_string(), }) @@ -2468,9 +2468,9 @@ pub async fn sandbox_resume( .into_diagnostic()? .into_inner() .sandbox - .ok_or_else(|| miette!("gateway returned no sandbox after resume"))?; + .ok_or_else(|| miette!("gateway returned no sandbox after start"))?; wait_for_lifecycle_phase(&mut client, sandbox, SandboxPhase::Ready).await?; - println!("{} Resumed sandbox {name}", "✓".green().bold()); + println!("{} Started sandbox {name}", "✓".green().bold()); Ok(()) } diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index 5b9fc87ab0..3d628f2c10 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -111,16 +111,16 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index 8569fd4cc2..60ffbd61f8 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -66,16 +66,16 @@ impl OpenShell for TestOpenShell { )) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 26ff7b2f62..a87ff0a6d8 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -129,16 +129,16 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 0314ee4417..3d411e91cf 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -114,16 +114,16 @@ impl OpenShell for TestOpenShell { })) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 23ad7ff258..41b93bab82 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -79,16 +79,16 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-core/src/telemetry.rs b/crates/openshell-core/src/telemetry.rs index f7a1cdfd1e..b2c9b79152 100644 --- a/crates/openshell-core/src/telemetry.rs +++ b/crates/openshell-core/src/telemetry.rs @@ -103,8 +103,8 @@ impl LifecycleResource { pub enum LifecycleOperation { Create, Delete, - Suspend, - Resume, + Stop, + Start, Update, } @@ -114,8 +114,8 @@ impl LifecycleOperation { match self { Self::Create => "create", Self::Delete => "delete", - Self::Suspend => "suspend", - Self::Resume => "resume", + Self::Stop => "stop", + Self::Start => "start", Self::Update => "update", } } diff --git a/crates/openshell-driver-docker/README.md b/crates/openshell-driver-docker/README.md index 0dd4320bdf..6f364a151c 100644 --- a/crates/openshell-driver-docker/README.md +++ b/crates/openshell-driver-docker/README.md @@ -18,12 +18,12 @@ The gateway runs as a host process. The Docker driver creates one container per sandbox and starts the `openshell-sandbox` supervisor inside that container. The supervisor then creates the nested sandbox namespace for the agent process. -## Suspend and Resume +## Stop and Start -Suspend stops the managed container without removing it. Docker retains the +Stop stops the managed container without removing it. Docker retains the container writable layer, attached volumes, labels, token material, and restart -policy. Resume starts that same container, so files in the resolved OCI -workspace remain available. A durably suspended sandbox is excluded from +policy. Start starts that same container, so files in the resolved OCI +workspace remain available. A durably stopped sandbox is excluded from gateway startup recovery and stays stopped across gateway restarts. Delete continues to force-remove the container and clean up driver-owned material. diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 3eb6d33151..08afed2fea 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -42,8 +42,8 @@ use openshell_core::proto::compute::v1::{ DriverSandboxTemplate, GatewayListenerRequirement, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, - GpuResourceRequirements, ListSandboxesRequest, ListSandboxesResponse, ResumeSandboxRequest, - ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, + GpuResourceRequirements, ListSandboxesRequest, ListSandboxesResponse, StartSandboxRequest, + StartSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, WatchSandboxesEvent, WatchSandboxesPlatformEvent, WatchSandboxesRequest, WatchSandboxesSandboxEvent, compute_driver_server::ComputeDriver, gateway_listener_requirement::Selector, @@ -901,14 +901,14 @@ impl DockerComputeDriver { } /// Start a managed sandbox container that was previously stopped. Used - /// by the gateway to resume sandboxes after a restart so that running + /// by the gateway to start sandboxes after a restart so that running /// state in the gateway store is matched by an actually-running /// container. /// /// Returns `Ok(true)` when a container existed and was started (or was /// already running), `Ok(false)` when no managed container is found for /// the sandbox, and `Err(...)` for any Docker failure. - pub async fn resume_sandbox( + pub async fn start_sandbox( &self, sandbox_id: &str, sandbox_name: &str, @@ -923,13 +923,13 @@ impl DockerComputeDriver { return Ok(false); }; let state = container.state.unwrap_or(ContainerSummaryStateEnum::EMPTY); - if !container_state_needs_resume(state) { + if !container_state_needs_start(state) { return Ok(true); } match self.docker.start_container(&target, None).await { Ok(()) => Ok(true), - // Already running — race with another resume path or the + // Already running — race with another start path or the // restart policy. Treat as success. Err(err) if is_not_modified_error(&err) => Ok(true), Err(err) if is_not_found_error(&err) => Ok(false), @@ -1485,18 +1485,18 @@ impl ComputeDriver for DockerComputeDriver { Ok(Response::new(StopSandboxResponse {})) } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let request = request.into_inner(); require_sandbox_identifier(&request.sandbox_id, &request.sandbox_name)?; - if !Self::resume_sandbox(self, &request.sandbox_id, &request.sandbox_name).await? { + if !Self::start_sandbox(self, &request.sandbox_id, &request.sandbox_name).await? { return Err(Status::not_found("sandbox not found")); } self.publish_container_snapshot(&request.sandbox_id, &request.sandbox_name) .await?; - Ok(Response::new(ResumeSandboxResponse {})) + Ok(Response::new(StartSandboxResponse {})) } async fn delete_sandbox( @@ -2983,7 +2983,7 @@ fn container_state_needs_shutdown_stop(state: ContainerSummaryStateEnum) -> bool /// `start_container`. Skip `Restarting` (already coming up), `Removing`, /// `Dead` (terminal), `Paused` (needs `unpause`, not `start`), and /// `Running` (nothing to do). -fn container_state_needs_resume(state: ContainerSummaryStateEnum) -> bool { +fn container_state_needs_start(state: ContainerSummaryStateEnum) -> bool { matches!( state, ContainerSummaryStateEnum::EXITED | ContainerSummaryStateEnum::CREATED diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index ac525c705c..9772bda407 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -2469,14 +2469,14 @@ fn extract_first_tar_entry_rejects_empty_archive() { } #[test] -fn container_state_needs_resume_matches_startable_states() { +fn container_state_needs_start_matches_startable_states() { for state in [ ContainerSummaryStateEnum::EXITED, ContainerSummaryStateEnum::CREATED, ] { assert!( - container_state_needs_resume(state), - "{state:?} should be resumed with Docker start", + container_state_needs_start(state), + "{state:?} should be started with Docker start", ); } @@ -2489,8 +2489,8 @@ fn container_state_needs_resume_matches_startable_states() { ContainerSummaryStateEnum::EMPTY, ] { assert!( - !container_state_needs_resume(state), - "{state:?} should not be resumed with Docker start", + !container_state_needs_start(state), + "{state:?} should not be started with Docker start", ); } } diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index fb79e25b1c..d9c0e17fd8 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -36,12 +36,12 @@ This is a stopgap persistence model. It preserves user files across pod rescheduling but duplicates the base workspace and does not automatically apply image updates to existing PVCs. Future snapshotting should replace it. -Suspend preserves the Agent Sandbox resource and workspace PVC while stopping +Stop preserves the Agent Sandbox resource and workspace PVC while stopping its pod. The driver sets `spec.operatingMode: Suspended` for `v1beta1` or -`spec.replicas: 0` for `v1alpha1`. Resume sets `Running` or one replica for the +`spec.replicas: 0` for `v1alpha1`. Start sets `Running` or one replica for the same resource, so the replacement pod mounts the existing claim. Delete is the only lifecycle operation that removes the Sandbox resource and its owned -storage. The driver confirms suspension from the published `Suspended` +storage. The driver confirms the stop from the published `Suspended` condition when available. Legacy `v1alpha1` controllers omit a zero replica count from status, so the driver confirms that their backing pod is gone. diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index e66b78bbb1..478297b82d 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -93,8 +93,8 @@ const KUBE_API_TIMEOUT: Duration = Duration::from_secs(30); /// Kubernetes defaults pod termination to 30 seconds when the pod template /// omits `terminationGracePeriodSeconds`. const DEFAULT_POD_TERMINATION_GRACE_PERIOD: Duration = Duration::from_secs(30); -const SUSPEND_INITIAL_POLL_INTERVAL: Duration = Duration::from_millis(250); -const SUSPEND_MAX_POLL_INTERVAL: Duration = Duration::from_secs(2); +const STOP_INITIAL_POLL_INTERVAL: Duration = Duration::from_millis(250); +const STOP_MAX_POLL_INTERVAL: Duration = Duration::from_secs(2); const SANDBOX_GROUP: &str = "agents.x-k8s.io"; const SANDBOX_VERSION_V1BETA1: &str = "v1beta1"; @@ -938,20 +938,20 @@ impl KubernetesComputeDriver { } pub async fn stop_sandbox(&self, sandbox_id: &str) -> Result<(), String> { - let (agent_sandbox_api, kube_name, pod_name, suspend_timeout) = self + let (agent_sandbox_api, kube_name, pod_name, stop_timeout) = self .patch_sandbox_operating_state(sandbox_id, false) .await?; let legacy_pod_api = (agent_sandbox_api.resource.version == SANDBOX_VERSION_V1ALPHA1) .then(|| Api::::namespaced(self.client.clone(), &self.config.namespace)); - let deadline = tokio::time::Instant::now() + suspend_timeout; - let mut poll_interval = SUSPEND_INITIAL_POLL_INTERVAL; + let deadline = tokio::time::Instant::now() + stop_timeout; + let mut poll_interval = STOP_INITIAL_POLL_INTERVAL; loop { let now = tokio::time::Instant::now(); if now >= deadline { return Err(format!( - "timed out after {}s waiting for Kubernetes sandbox to suspend", - suspend_timeout.as_secs() + "timed out after {}s waiting for Kubernetes sandbox to stop", + stop_timeout.as_secs() )); } let request_timeout = KUBE_API_TIMEOUT.min(deadline.saturating_duration_since(now)); @@ -959,15 +959,15 @@ impl KubernetesComputeDriver { request_timeout, agent_sandbox_api.api.get(&kube_name), ) - .await - .map_err(|_| { - format!( - "timed out after {}s waiting for Kubernetes API while checking sandbox suspension", - request_timeout.as_secs() - ) - })? - .map_err(|err| err.to_string())?; - if kubernetes_sandbox_has_suspended_condition(&object) { + .await + .map_err(|_| { + format!( + "timed out after {}s waiting for Kubernetes API while checking sandbox stop", + request_timeout.as_secs() + ) + })? + .map_err(|err| err.to_string())?; + if kubernetes_sandbox_has_stopped_condition(&object) { return Ok(()); } if let Some(pod_api) = legacy_pod_api.as_ref() @@ -978,16 +978,16 @@ impl KubernetesComputeDriver { let now = tokio::time::Instant::now(); if now >= deadline { return Err(format!( - "timed out after {}s waiting for Kubernetes sandbox to suspend", - suspend_timeout.as_secs() + "timed out after {}s waiting for Kubernetes sandbox to stop", + stop_timeout.as_secs() )); } tokio::time::sleep(poll_interval.min(deadline.saturating_duration_since(now))).await; - poll_interval = next_suspend_poll_interval(poll_interval); + poll_interval = next_stop_poll_interval(poll_interval); } } - pub async fn resume_sandbox(&self, sandbox_id: &str) -> Result<(), String> { + pub async fn start_sandbox(&self, sandbox_id: &str) -> Result<(), String> { self.patch_sandbox_operating_state(sandbox_id, true) .await .map(|_| ()) @@ -1022,7 +1022,7 @@ impl KubernetesComputeDriver { .into_iter() .next() .ok_or_else(|| "sandbox not found".to_string())?; - let suspend_timeout = kubernetes_sandbox_suspend_timeout(&object); + let stop_timeout = kubernetes_sandbox_stop_timeout(&object); let kube_name = object .metadata .name @@ -1063,7 +1063,7 @@ impl KubernetesComputeDriver { running, "Updated Kubernetes sandbox operating state" ); - Ok((agent_sandbox_api, kube_name, pod_name, suspend_timeout)) + Ok((agent_sandbox_api, kube_name, pod_name, stop_timeout)) } pub async fn delete_sandbox(&self, sandbox_id: &str) -> Result { @@ -3260,7 +3260,7 @@ fn status_from_object(obj: &DynamicObject) -> Option { }) } -fn kubernetes_sandbox_has_suspended_condition(obj: &DynamicObject) -> bool { +fn kubernetes_sandbox_has_stopped_condition(obj: &DynamicObject) -> bool { obj.data .get("status") .and_then(|status| status.get("conditions")) @@ -3298,7 +3298,7 @@ async fn kubernetes_sandbox_pod_is_gone( } } -fn kubernetes_sandbox_suspend_timeout(obj: &DynamicObject) -> Duration { +fn kubernetes_sandbox_stop_timeout(obj: &DynamicObject) -> Duration { let termination_grace_period = obj .data .get("spec") @@ -3314,8 +3314,8 @@ fn kubernetes_sandbox_suspend_timeout(obj: &DynamicObject) -> Duration { termination_grace_period.saturating_add(KUBE_API_TIMEOUT) } -fn next_suspend_poll_interval(current: Duration) -> Duration { - current.saturating_mul(2).min(SUSPEND_MAX_POLL_INTERVAL) +fn next_stop_poll_interval(current: Duration) -> Duration { + current.saturating_mul(2).min(STOP_MAX_POLL_INTERVAL) } fn sandbox_operating_state_patch( @@ -3411,19 +3411,19 @@ mod tests { #[test] fn lifecycle_patch_uses_version_specific_operating_state() { - let beta_suspend = sandbox_operating_state_patch(SANDBOX_VERSION_V1BETA1, "42", false); - assert_eq!(beta_suspend["metadata"]["resourceVersion"], "42"); - assert_eq!(beta_suspend["spec"]["operatingMode"], "Suspended"); - assert!(beta_suspend["spec"].get("replicas").is_none()); + let beta_stop = sandbox_operating_state_patch(SANDBOX_VERSION_V1BETA1, "42", false); + assert_eq!(beta_stop["metadata"]["resourceVersion"], "42"); + assert_eq!(beta_stop["spec"]["operatingMode"], "Suspended"); + assert!(beta_stop["spec"].get("replicas").is_none()); - let alpha_resume = sandbox_operating_state_patch(SANDBOX_VERSION_V1ALPHA1, "43", true); - assert_eq!(alpha_resume["metadata"]["resourceVersion"], "43"); - assert_eq!(alpha_resume["spec"]["replicas"], 1); - assert!(alpha_resume["spec"].get("operatingMode").is_none()); + let alpha_start = sandbox_operating_state_patch(SANDBOX_VERSION_V1ALPHA1, "43", true); + assert_eq!(alpha_start["metadata"]["resourceVersion"], "43"); + assert_eq!(alpha_start["spec"]["replicas"], 1); + assert!(alpha_start["spec"].get("operatingMode").is_none()); } #[test] - fn suspend_timeout_includes_pod_grace_period_and_reconcile_headroom() { + fn stop_timeout_includes_pod_grace_period_and_reconcile_headroom() { let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( SANDBOX_GROUP, SANDBOX_VERSION_V1BETA1, @@ -3432,7 +3432,7 @@ mod tests { let mut sandbox = DynamicObject::new("sandbox", &resource); assert_eq!( - kubernetes_sandbox_suspend_timeout(&sandbox), + kubernetes_sandbox_stop_timeout(&sandbox), Duration::from_secs(60), "an omitted grace period uses the Kubernetes 30-second default" ); @@ -3445,14 +3445,14 @@ mod tests { } }); assert_eq!( - kubernetes_sandbox_suspend_timeout(&sandbox), + kubernetes_sandbox_stop_timeout(&sandbox), Duration::from_secs(75) ); } #[test] - fn suspend_poll_interval_backs_off_to_cap() { - let mut interval = SUSPEND_INITIAL_POLL_INTERVAL; + fn stop_poll_interval_backs_off_to_cap() { + let mut interval = STOP_INITIAL_POLL_INTERVAL; let expected = [ Duration::from_millis(500), Duration::from_secs(1), @@ -3461,13 +3461,13 @@ mod tests { ]; for expected_interval in expected { - interval = next_suspend_poll_interval(interval); + interval = next_stop_poll_interval(interval); assert_eq!(interval, expected_interval); } } #[test] - fn suspended_status_requires_published_condition() { + fn stopped_status_requires_published_condition() { let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( SANDBOX_GROUP, SANDBOX_VERSION_V1ALPHA1, @@ -3477,7 +3477,7 @@ mod tests { sandbox.data = serde_json::json!({"status": {"replicas": 0}}); assert!( - !kubernetes_sandbox_has_suspended_condition(&sandbox), + !kubernetes_sandbox_has_stopped_condition(&sandbox), "v1alpha1 omits a zero status replica count on the wire; it is not a usable completion signal" ); @@ -3486,7 +3486,7 @@ mod tests { "conditions": [{"type": "Suspended", "status": "True"}] } }); - assert!(kubernetes_sandbox_has_suspended_condition(&sandbox)); + assert!(kubernetes_sandbox_has_stopped_condition(&sandbox)); } #[test] diff --git a/crates/openshell-driver-kubernetes/src/grpc.rs b/crates/openshell-driver-kubernetes/src/grpc.rs index e3b81d27fd..ef2e2686e4 100644 --- a/crates/openshell-driver-kubernetes/src/grpc.rs +++ b/crates/openshell-driver-kubernetes/src/grpc.rs @@ -8,7 +8,7 @@ use openshell_core::proto::compute::v1::{ CreateSandboxRequest, CreateSandboxResponse, DeleteSandboxRequest, DeleteSandboxResponse, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, - ListSandboxesRequest, ListSandboxesResponse, ResumeSandboxRequest, ResumeSandboxResponse, + ListSandboxesRequest, ListSandboxesResponse, StartSandboxRequest, StartSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, WatchSandboxesRequest, compute_driver_server::ComputeDriver, @@ -126,19 +126,19 @@ impl ComputeDriver for ComputeDriverService { Ok(Response::new(StopSandboxResponse {})) } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let request = request.into_inner(); if request.sandbox_id.is_empty() { return Err(Status::invalid_argument("sandbox_id is required")); } self.driver - .resume_sandbox(&request.sandbox_id) + .start_sandbox(&request.sandbox_id) .await .map_err(kubernetes_lifecycle_status)?; - Ok(Response::new(ResumeSandboxResponse {})) + Ok(Response::new(StartSandboxResponse {})) } async fn delete_sandbox( diff --git a/crates/openshell-driver-podman/README.md b/crates/openshell-driver-podman/README.md index 3129b84be2..4cec6a4371 100644 --- a/crates/openshell-driver-podman/README.md +++ b/crates/openshell-driver-podman/README.md @@ -19,11 +19,11 @@ independently. For a rootless networking deep dive, see [NETWORKING.md](NETWORKING.md). -## Suspend and Resume +## Stop and Start -Suspend stops the managed container without deleting it. The per-sandbox named +Stop stops the managed container without deleting it. The per-sandbox named workspace volume, token and proxy-auth secrets, labels, and container metadata -remain intact. Resume starts the same container and reuses the same named +remain intact. Start starts the same container and reuses the same named volume. Stopped managed containers remain visible through list and watch reconciliation. Delete remains responsible for removing the container, driver-owned secrets, and workspace volume. diff --git a/crates/openshell-driver-podman/src/driver.rs b/crates/openshell-driver-podman/src/driver.rs index 26f9b2f1a3..a1619b15ce 100644 --- a/crates/openshell-driver-podman/src/driver.rs +++ b/crates/openshell-driver-podman/src/driver.rs @@ -849,8 +849,8 @@ impl PodmanComputeDriver { .map_err(ComputeDriverError::from) } - /// Resume a previously stopped sandbox container. - pub async fn resume_sandbox(&self, sandbox_id: &str) -> Result<(), ComputeDriverError> { + /// Start a previously stopped sandbox container. + pub async fn start_sandbox(&self, sandbox_id: &str) -> Result<(), ComputeDriverError> { let container = self .find_container(sandbox_id) .await? @@ -859,7 +859,7 @@ impl PodmanComputeDriver { return Ok(()); } let container_id = container.id; - info!(sandbox_id = %sandbox_id, container = %container_id, "Resuming sandbox container"); + info!(sandbox_id = %sandbox_id, container = %container_id, "Starting sandbox container"); self.client .start_container(&container_id) @@ -1206,7 +1206,7 @@ mod tests { } #[tokio::test] - async fn stop_and_resume_target_the_existing_container() { + async fn stop_and_start_target_the_existing_container() { let (stop_socket, stop_requests, stop_handle) = spawn_podman_stub( "lifecycle-stop", vec![ @@ -1237,9 +1237,9 @@ mod tests { ], ); test_driver(start_socket.clone()) - .resume_sandbox("sandbox-1") + .start_sandbox("sandbox-1") .await - .expect("resume should succeed"); + .expect("start should succeed"); start_handle.await.expect("start stub should finish"); assert_eq!( start_requests diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index ea5d54ac28..8d34660514 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -8,7 +8,7 @@ use openshell_core::proto::compute::v1::{ CreateSandboxRequest, CreateSandboxResponse, DeleteSandboxRequest, DeleteSandboxResponse, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, - ListSandboxesRequest, ListSandboxesResponse, ResumeSandboxRequest, ResumeSandboxResponse, + ListSandboxesRequest, ListSandboxesResponse, StartSandboxRequest, StartSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, WatchSandboxesRequest, compute_driver_server::ComputeDriver, @@ -128,19 +128,19 @@ impl ComputeDriver for ComputeDriverService { Ok(Response::new(StopSandboxResponse {})) } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let request = request.into_inner(); if request.sandbox_id.is_empty() { return Err(Status::invalid_argument("sandbox_id is required")); } self.driver - .resume_sandbox(&request.sandbox_id) + .start_sandbox(&request.sandbox_id) .await .map_err(Status::from)?; - Ok(Response::new(ResumeSandboxResponse {})) + Ok(Response::new(StartSandboxResponse {})) } async fn delete_sandbox( diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index a26e049c07..10ffac2ba1 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -197,14 +197,14 @@ The driver also writes the accepted `DriverSandbox` launch request to new VM driver process; that process scans the sandbox state directories, restarts each persisted VM launcher, and preserves any existing `overlay.ext4` instead of cloning a fresh overlay template. If a restart happened before the -overlay was created, the driver creates it during the resume attempt. +overlay was created, the driver creates it during the start attempt. -Suspension writes a marker in the sandbox state directory before terminating +Stop writes a marker in the sandbox state directory before terminating the launcher and releasing host GPU and network allocations. It retains `sandbox.pb`, `overlay.ext4`, and lifecycle-extension state. Startup registers -marked sandboxes without launching compute. Resume removes the marker and uses +marked sandboxes without launching compute. Start removes the marker and uses the normal persisted restore path with the existing overlay. Delete removes the -entire sandbox state directory, including a suspended marker and overlay. +entire sandbox state directory, including a stop marker and overlay. ## Logs and debugging diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index f9f8ef95bb..ce4e1d2d9c 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -43,7 +43,7 @@ use openshell_core::proto::compute::v1::{ DriverSandboxTemplate as SandboxTemplate, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, ListSandboxesRequest, ListSandboxesResponse, - ResumeSandboxRequest, ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, + StartSandboxRequest, StartSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, WatchSandboxesEvent, WatchSandboxesPlatformEvent, WatchSandboxesRequest, WatchSandboxesSandboxEvent, compute_driver_server::ComputeDriver, watch_sandboxes_event, @@ -166,7 +166,7 @@ const OVERLAY_TEMPLATE_CACHE_DIR: &str = "overlay-templates"; const OVERLAY_TEMPLATE_CACHE_LAYOUT_VERSION: &str = "sandbox-overlay-ext4-v1"; const SANDBOX_OVERLAY_IMAGE: &str = "overlay.ext4"; const SANDBOX_REQUEST_FILE: &str = "sandbox.pb"; -const SANDBOX_SUSPENDED_FILE: &str = "suspended"; +const SANDBOX_STOPPED_FILE: &str = "stopped"; const GUEST_IMAGE_CONFIG_DIR: &str = "openshell-image"; const GUEST_IMAGE_OCI_LAYOUT_DIR: &str = "oci"; const GUEST_IMAGE_OCI_REF: &str = "openshell"; @@ -605,7 +605,7 @@ impl VmDriver { registry.remove(&sandbox.id); let _ = tokio::fs::remove_dir_all(&state_dir).await; return Err(Status::internal(format!( - "write sandbox resume metadata failed: {err}" + "write sandbox start metadata failed: {err}" ))); } @@ -1087,9 +1087,9 @@ impl VmDriver { // Persist intent before detaching process handles or releasing host // allocations. If this write fails, the live record remains intact. - tokio::fs::write(state_dir.join(SANDBOX_SUSPENDED_FILE), b"suspended\n") + tokio::fs::write(state_dir.join(SANDBOX_STOPPED_FILE), b"stopped\n") .await - .map_err(|err| Status::internal(format!("persist suspension marker failed: {err}")))?; + .map_err(|err| Status::internal(format!("persist stop marker failed: {err}")))?; let (process, provisioning_task, has_gpu, has_qemu_network, snapshot) = { let mut registry = self.registry.lock().await; @@ -1116,29 +1116,24 @@ impl VmDriver { .map_err(|err| Status::internal(format!("failed to stop vm: {err}")))?; } self.lifecycle_extensions - .after_launch_failed(&snapshot, &state_dir, LaunchAbortReason::Suspended) + .after_launch_failed(&snapshot, &state_dir, LaunchAbortReason::Stopped) .await; self.release_allocations(&record_id, has_gpu, has_qemu_network); if let Some(snapshot) = self - .set_snapshot_condition(&record_id, suspended_condition(), false) + .set_snapshot_condition(&record_id, stopped_condition(), false) .await { self.publish_snapshot(snapshot); } self.publish_platform_event( record_id, - platform_event( - "vm", - "Normal", - "Suspended", - "VM sandbox suspended".to_string(), - ), + platform_event("vm", "Normal", "Stopped", "VM sandbox stopped".to_string()), ); Ok(()) } - pub async fn resume_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result<(), Status> { + pub async fn start_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result<(), Status> { if !sandbox_id.is_empty() { validate_sandbox_id(sandbox_id)?; } @@ -1165,9 +1160,9 @@ impl VmDriver { let sandbox = read_sandbox_request(&state_dir.join(SANDBOX_REQUEST_FILE)) .await .map_err(|err| { - Status::internal(format!("read sandbox resume metadata failed: {err}")) + Status::internal(format!("read sandbox start metadata failed: {err}")) })?; - let suspended_record = self + let stopped_record = self .registry .lock() .await @@ -1181,8 +1176,8 @@ impl VmDriver { .lock() .await .entry(record_id) - .or_insert(suspended_record); - return Err(Status::internal("failed to resume persisted VM sandbox")); + .or_insert(stopped_record); + return Err(Status::internal("failed to start persisted VM sandbox")); } Ok(()) } @@ -1393,11 +1388,11 @@ impl VmDriver { continue; } - if tokio::fs::metadata(state_dir.join(SANDBOX_SUSPENDED_FILE)) + if tokio::fs::metadata(state_dir.join(SANDBOX_STOPPED_FILE)) .await .is_ok() { - let snapshot = sandbox_snapshot(&sandbox, suspended_condition(), false); + let snapshot = sandbox_snapshot(&sandbox, stopped_condition(), false); let mut registry = self.registry.lock().await; registry.entry(sandbox.id.clone()).or_insert(SandboxRecord { snapshot: snapshot.clone(), @@ -1410,7 +1405,7 @@ impl VmDriver { }); drop(registry); self.publish_snapshot(snapshot); - info!(sandbox_id = %sandbox.id, "vm driver: restored suspended sandbox without launching compute"); + info!(sandbox_id = %sandbox.id, "vm driver: restored stopped sandbox without launching compute"); continue; } @@ -1420,14 +1415,14 @@ impl VmDriver { } /// Restore a persisted sandbox and report whether the driver accepted it. - /// For explicit resume, the suspension marker is cleared only after all + /// For explicit start, the stop marker is cleared only after all /// restore preflight checks pass and the replacement registry record is - /// installed. A failed restore therefore remains durably suspended. + /// installed. A failed restore therefore remains durably stopped. async fn restore_persisted_sandbox( &self, sandbox: Sandbox, state_dir: PathBuf, - clear_suspension_marker: bool, + clear_stop_marker: bool, reconciliation_span: &tracing::Span, ) -> bool { let Some(image_ref) = self.resolved_sandbox_image(&sandbox) else { @@ -1497,8 +1492,8 @@ impl VmDriver { ); } - if clear_suspension_marker { - match tokio::fs::remove_file(state_dir.join(SANDBOX_SUSPENDED_FILE)).await { + if clear_stop_marker { + match tokio::fs::remove_file(state_dir.join(SANDBOX_STOPPED_FILE)).await { Ok(()) => {} Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} Err(err) => { @@ -1507,7 +1502,7 @@ impl VmDriver { sandbox_id = %sandbox.id, state_dir = %state_dir.display(), error = %err, - "vm driver: cannot clear suspension marker for persisted sandbox restore" + "vm driver: cannot clear stop marker for persisted sandbox restore" ); return false; } @@ -3360,14 +3355,14 @@ impl ComputeDriver for VmDriver { Ok(Response::new(StopSandboxResponse {})) } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let request = request.into_inner(); - self.resume_sandbox(&request.sandbox_id, &request.sandbox_name) + self.start_sandbox(&request.sandbox_id, &request.sandbox_name) .await?; - Ok(Response::new(ResumeSandboxResponse {})) + Ok(Response::new(StartSandboxResponse {})) } async fn delete_sandbox( @@ -5368,9 +5363,9 @@ fn deleting_condition() -> SandboxCondition { } } -fn suspended_condition() -> SandboxCondition { +fn stopped_condition() -> SandboxCondition { SandboxCondition { - r#type: "Suspended".to_string(), + r#type: "Stopped".to_string(), status: "True".to_string(), reason: "ComputeStopped".to_string(), message: "VM compute is stopped and persistent state is retained".to_string(), @@ -6543,13 +6538,13 @@ mod tests { } #[tokio::test] - async fn sandbox_request_metadata_round_trips_for_resume() { + async fn sandbox_request_metadata_round_trips_for_start() { let base = unique_temp_dir(); let state_dir = base.join("sandboxes").join("sandbox-123"); std::fs::create_dir_all(&state_dir).unwrap(); let sandbox = Sandbox { id: "sandbox-123".to_string(), - name: "resume-sandbox".to_string(), + name: "start-sandbox".to_string(), namespace: "vm-dev".to_string(), spec: Some(SandboxSpec { environment: HashMap::from([("KEY".to_string(), "value".to_string())]), @@ -6590,22 +6585,22 @@ mod tests { } #[tokio::test] - async fn failed_resume_preserves_suspended_state() { + async fn failed_start_preserves_stopped_state() { let temp = tempfile::tempdir().unwrap(); let mut driver = test_driver_with_extensions(LifecycleExtensionRegistry::new()); driver.config.state_dir = temp.path().to_path_buf(); let sandbox = Sandbox { - id: "sandbox-suspended".to_string(), - name: "suspended".to_string(), + id: "sandbox-stopped".to_string(), + name: "stopped".to_string(), ..Default::default() }; let state_dir = temp.path().join("sandboxes").join(&sandbox.id); create_private_dir_all(&state_dir).await.unwrap(); write_sandbox_request(&state_dir, &sandbox).await.unwrap(); - tokio::fs::write(state_dir.join(SANDBOX_SUSPENDED_FILE), b"suspended\n") + tokio::fs::write(state_dir.join(SANDBOX_STOPPED_FILE), b"stopped\n") .await .unwrap(); - let snapshot = sandbox_snapshot(&sandbox, suspended_condition(), false); + let snapshot = sandbox_snapshot(&sandbox, stopped_condition(), false); driver.registry.lock().await.insert( sandbox.id.clone(), SandboxRecord { @@ -6620,33 +6615,33 @@ mod tests { ); let err = driver - .resume_sandbox(&sandbox.id, &sandbox.name) + .start_sandbox(&sandbox.id, &sandbox.name) .await - .expect_err("resume without an image should fail"); + .expect_err("start without an image should fail"); assert_eq!(err.code(), Code::Internal); assert!( - tokio::fs::metadata(state_dir.join(SANDBOX_SUSPENDED_FILE)) + tokio::fs::metadata(state_dir.join(SANDBOX_STOPPED_FILE)) .await .is_ok(), - "failed resume must retain its durable suspension marker" + "failed start must retain its durable stop marker" ); let restored = driver .get_sandbox(&sandbox.id, &sandbox.name) .await .unwrap() - .expect("failed resume must retain its suspended registry record"); + .expect("failed start must retain its stopped registry record"); let condition = restored .status .as_ref() .and_then(|status| status.conditions.first()) - .expect("suspended condition"); - assert_eq!(condition.r#type, "Suspended"); + .expect("stopped condition"); + assert_eq!(condition.r#type, "Stopped"); assert_eq!(condition.status, "True"); } #[test] - fn prepare_sandbox_overlay_preserves_existing_overlay_on_resume() { + fn prepare_sandbox_overlay_preserves_existing_overlay_on_start() { let base = unique_temp_dir(); std::fs::create_dir_all(&base).unwrap(); let template = base.join("template.ext4"); @@ -6670,7 +6665,7 @@ mod tests { } #[test] - fn prepare_sandbox_overlay_creates_missing_overlay_on_resume() { + fn prepare_sandbox_overlay_creates_missing_overlay_on_start() { let base = unique_temp_dir(); std::fs::create_dir_all(&base).unwrap(); let template = base.join("template.ext4"); diff --git a/crates/openshell-driver-vm/src/lifecycle.rs b/crates/openshell-driver-vm/src/lifecycle.rs index 5ab2ff01b8..25ec91db67 100644 --- a/crates/openshell-driver-vm/src/lifecycle.rs +++ b/crates/openshell-driver-vm/src/lifecycle.rs @@ -32,8 +32,8 @@ pub enum LaunchAbortReason { /// opportunity to release host resources they allocated in /// [`LifecycleExtension::before_launch`]. ProcessExited, - /// The gateway intentionally suspended the sandbox while retaining disk state. - Suspended, + /// The gateway intentionally stopped the sandbox while retaining disk state. + Stopped, } #[derive(Debug, Clone)] diff --git a/crates/openshell-driver-vm/src/otel_tracing.rs b/crates/openshell-driver-vm/src/otel_tracing.rs index df950b026c..adfeb896c6 100644 --- a/crates/openshell-driver-vm/src/otel_tracing.rs +++ b/crates/openshell-driver-vm/src/otel_tracing.rs @@ -74,7 +74,7 @@ fn compute_driver_rpc_operation(path: &str) -> (&'static str, &'static str) { Some("GetSandbox") => ("driver.get_sandbox", "get_sandbox"), Some("ListSandboxes") => ("driver.list_sandboxes", "list_sandboxes"), Some("StopSandbox") => ("driver.stop_sandbox", "stop_sandbox"), - Some("ResumeSandbox") => ("driver.resume_sandbox", "resume_sandbox"), + Some("StartSandbox") => ("driver.start_sandbox", "start_sandbox"), Some("DeleteSandbox") => ("driver.delete_sandbox", "delete_sandbox"), Some("WatchSandboxes") => ("driver.watch_sandboxes", "watch_sandboxes"), _ => ("driver.unknown", "unknown"), @@ -178,7 +178,7 @@ mod tests { ("GetSandbox", "driver.get_sandbox", "get_sandbox"), ("ListSandboxes", "driver.list_sandboxes", "list_sandboxes"), ("StopSandbox", "driver.stop_sandbox", "stop_sandbox"), - ("ResumeSandbox", "driver.resume_sandbox", "resume_sandbox"), + ("StartSandbox", "driver.start_sandbox", "start_sandbox"), ("DeleteSandbox", "driver.delete_sandbox", "delete_sandbox"), ( "WatchSandboxes", diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index b2c08ecf3a..c67e91e219 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -217,29 +217,29 @@ impl OpenShellClient { Ok(response.deleted) } - /// Suspend a sandbox by name. - pub async fn suspend_sandbox(&self, name: &str) -> Result { + /// Stop a sandbox by name. + pub async fn stop_sandbox(&self, name: &str) -> Result { let response = self .unary(|mut grpc| { - let request = proto::SuspendSandboxRequest { + let request = proto::StopSandboxRequest { name: name.to_string(), workspace: String::new(), }; - async move { grpc.suspend_sandbox(request).await } + async move { grpc.stop_sandbox(request).await } }) .await?; sandbox_from_response(response.sandbox) } - /// Resume a suspended sandbox by name. - pub async fn resume_sandbox(&self, name: &str) -> Result { + /// Start a stopped sandbox by name. + pub async fn start_sandbox(&self, name: &str) -> Result { let response = self .unary(|mut grpc| { - let request = proto::ResumeSandboxRequest { + let request = proto::StartSandboxRequest { name: name.to_string(), workspace: String::new(), }; - async move { grpc.resume_sandbox(request).await } + async move { grpc.start_sandbox(request).await } }) .await?; sandbox_from_response(response.sandbox) @@ -614,31 +614,31 @@ impl WorkspaceScopedClient { Ok(response.deleted) } - /// Suspend a sandbox by name in this workspace. - pub async fn suspend_sandbox(&self, name: &str) -> Result { + /// Stop a sandbox by name in this workspace. + pub async fn stop_sandbox(&self, name: &str) -> Result { let response = self .client .unary(|mut grpc| { - let request = proto::SuspendSandboxRequest { + let request = proto::StopSandboxRequest { name: name.to_string(), workspace: self.workspace.clone(), }; - async move { grpc.suspend_sandbox(request).await } + async move { grpc.stop_sandbox(request).await } }) .await?; sandbox_from_response(response.sandbox) } - /// Resume a suspended sandbox by name in this workspace. - pub async fn resume_sandbox(&self, name: &str) -> Result { + /// Start a stopped sandbox by name in this workspace. + pub async fn start_sandbox(&self, name: &str) -> Result { let response = self .client .unary(|mut grpc| { - let request = proto::ResumeSandboxRequest { + let request = proto::StartSandboxRequest { name: name.to_string(), workspace: self.workspace.clone(), }; - async move { grpc.resume_sandbox(request).await } + async move { grpc.start_sandbox(request).await } }) .await?; sandbox_from_response(response.sandbox) diff --git a/crates/openshell-sdk/src/raw.rs b/crates/openshell-sdk/src/raw.rs index 4619d3d743..e974b19259 100644 --- a/crates/openshell-sdk/src/raw.rs +++ b/crates/openshell-sdk/src/raw.rs @@ -24,9 +24,9 @@ pub use openshell_core::proto::open_shell_client::OpenShellClient as GrpcClient; pub use openshell_core::proto::{ CreateSandboxRequest, CreateWorkspaceRequest, DeleteSandboxRequest, DeleteWorkspaceRequest, ExecSandboxRequest, GetSandboxRequest, GetWorkspaceRequest, HealthRequest, - ListProvidersRequest, ListSandboxesRequest, ListWorkspacesRequest, ResumeSandboxRequest, - Sandbox, SandboxPhase as ProtoSandboxPhase, SandboxSpec as ProtoSandboxSpec, SandboxTemplate, - ServiceStatus as ProtoServiceStatus, SuspendSandboxRequest, Workspace, + ListProvidersRequest, ListSandboxesRequest, ListWorkspacesRequest, Sandbox, + SandboxPhase as ProtoSandboxPhase, SandboxSpec as ProtoSandboxSpec, SandboxTemplate, + ServiceStatus as ProtoServiceStatus, StartSandboxRequest, StopSandboxRequest, Workspace, }; /// Type alias for the gRPC client wrapped in the SDK's auth interceptor. diff --git a/crates/openshell-sdk/src/types.rs b/crates/openshell-sdk/src/types.rs index 987201f33b..6f179499c9 100644 --- a/crates/openshell-sdk/src/types.rs +++ b/crates/openshell-sdk/src/types.rs @@ -62,9 +62,9 @@ pub enum SandboxPhase { Error, Deleting, Unknown, - Suspending, - Suspended, - Resuming, + Stopping, + Stopped, + Starting, } impl From for SandboxPhase { @@ -76,9 +76,9 @@ impl From for SandboxPhase { proto::SandboxPhase::Error => Self::Error, proto::SandboxPhase::Deleting => Self::Deleting, proto::SandboxPhase::Unknown => Self::Unknown, - proto::SandboxPhase::Suspending => Self::Suspending, - proto::SandboxPhase::Suspended => Self::Suspended, - proto::SandboxPhase::Resuming => Self::Resuming, + proto::SandboxPhase::Stopping => Self::Stopping, + proto::SandboxPhase::Stopped => Self::Stopped, + proto::SandboxPhase::Starting => Self::Starting, } } } diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index aff6bf6829..09e91330ce 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -31,8 +31,8 @@ struct MockState { last_create: Mutex>, last_delete_name: Mutex>, last_delete_workspace: Mutex>, - last_suspend: Mutex>, - last_resume: Mutex>, + last_stop: Mutex>, + last_start: Mutex>, last_list_request: Mutex>, last_exec_request: Mutex>, last_workspace_request: Mutex>, @@ -163,33 +163,33 @@ impl OpenShell for TestOpenShell { })) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - request: tonic::Request, + request: tonic::Request, ) -> Result, Status> { let request = request.into_inner(); let sandbox = sandbox_with_phase_ws( &request.name, - proto::SandboxPhase::Suspended, + proto::SandboxPhase::Stopped, &request.workspace, ); - *self.state.last_suspend.lock().await = Some(request); + *self.state.last_stop.lock().await = Some(request); Ok(Response::new(proto::SandboxResponse { sandbox: Some(sandbox), })) } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: tonic::Request, + request: tonic::Request, ) -> Result, Status> { let request = request.into_inner(); let sandbox = sandbox_with_phase_ws( &request.name, - proto::SandboxPhase::Resuming, + proto::SandboxPhase::Starting, &request.workspace, ); - *self.state.last_resume.lock().await = Some(request); + *self.state.last_start.lock().await = Some(request); Ok(Response::new(proto::SandboxResponse { sandbox: Some(sandbox), })) @@ -851,26 +851,26 @@ async fn delete_sandbox_returns_server_ack() { } #[tokio::test] -async fn suspend_and_resume_map_requests_and_phases() { +async fn stop_and_start_map_requests_and_phases() { let state = Arc::new(MockState::default()); let endpoint = start_mock(state.clone()).await; let client = connect(&endpoint).await; - let suspended = client.suspend_sandbox("sleepy").await.unwrap(); - assert_eq!(suspended.phase, SandboxPhase::Suspended); - let suspend = state.last_suspend.lock().await.clone().unwrap(); - assert_eq!(suspend.name, "sleepy"); - assert!(suspend.workspace.is_empty()); + let stopped = client.stop_sandbox("sleepy").await.unwrap(); + assert_eq!(stopped.phase, SandboxPhase::Stopped); + let stop = state.last_stop.lock().await.clone().unwrap(); + assert_eq!(stop.name, "sleepy"); + assert!(stop.workspace.is_empty()); - let resumed = client + let started = client .workspace("team-a") - .resume_sandbox("sleepy") + .start_sandbox("sleepy") .await .unwrap(); - assert_eq!(resumed.phase, SandboxPhase::Resuming); - let resume = state.last_resume.lock().await.clone().unwrap(); - assert_eq!(resume.name, "sleepy"); - assert_eq!(resume.workspace, "team-a"); + assert_eq!(started.phase, SandboxPhase::Starting); + let start = state.last_start.lock().await.clone().unwrap(); + assert_eq!(start.name, "sleepy"); + assert_eq!(start.workspace, "team-a"); } #[tokio::test] diff --git a/crates/openshell-server/src/auth/method_authz.rs b/crates/openshell-server/src/auth/method_authz.rs index 557e334e21..71eb7acac6 100644 --- a/crates/openshell-server/src/auth/method_authz.rs +++ b/crates/openshell-server/src/auth/method_authz.rs @@ -152,8 +152,8 @@ mod tests { #[test] fn sandbox_lifecycle_mutations_require_user_write_authority() { for path in [ - "/openshell.v1.OpenShell/SuspendSandbox", - "/openshell.v1.OpenShell/ResumeSandbox", + "/openshell.v1.OpenShell/StopSandbox", + "/openshell.v1.OpenShell/StartSandbox", ] { let entry = lookup(path).expect("lifecycle RPC must have auth metadata"); assert_eq!(entry.auth_mode, AuthMode::Bearer); diff --git a/crates/openshell-server/src/auth/sandbox_methods.rs b/crates/openshell-server/src/auth/sandbox_methods.rs index 9fb46c68fd..5cc9e3693a 100644 --- a/crates/openshell-server/src/auth/sandbox_methods.rs +++ b/crates/openshell-server/src/auth/sandbox_methods.rs @@ -42,12 +42,8 @@ mod tests { assert!(!is_sandbox_callable( "/openshell.v1.OpenShell/DeleteSandbox" )); - assert!(!is_sandbox_callable( - "/openshell.v1.OpenShell/SuspendSandbox" - )); - assert!(!is_sandbox_callable( - "/openshell.v1.OpenShell/ResumeSandbox" - )); + assert!(!is_sandbox_callable("/openshell.v1.OpenShell/StopSandbox")); + assert!(!is_sandbox_callable("/openshell.v1.OpenShell/StartSandbox")); assert!(!is_sandbox_callable( "/openshell.v1.OpenShell/CreateProvider" )); diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 4f528f5626..94308f88f5 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -32,7 +32,7 @@ use openshell_core::proto::compute::v1::{ GetCapabilitiesRequest, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GpuResourceRequirements as DriverGpuResourceRequirements, ListSandboxesRequest, - ResourceRequirements as DriverSandboxResourceRequirements, ResumeSandboxRequest, + ResourceRequirements as DriverSandboxResourceRequirements, StartSandboxRequest, StopSandboxRequest, ValidateSandboxCreateRequest, WatchSandboxesEvent, WatchSandboxesRequest, compute_driver_client::ComputeDriverClient, compute_driver_server::ComputeDriver, gateway_listener_requirement::Selector, watch_sandboxes_event, @@ -282,20 +282,20 @@ impl ShutdownCleanup for DockerComputeDriver { } } -/// Resume a single sandbox whose store record indicates it should be +/// Start a single sandbox whose store record indicates it should be /// running. Implemented by drivers (currently only Docker) where compute /// resources do not auto-restart with the gateway. Returns `Ok(true)` if -/// the backend resource was found and resumed (or was already running), +/// the backend resource was found and started (or was already running), /// `Ok(false)` if no backend resource exists. #[tonic::async_trait] -trait StartupResume: Send + Sync { - async fn resume_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result; +trait StartupSandboxStarter: Send + Sync { + async fn start_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result; } #[tonic::async_trait] -impl StartupResume for DockerComputeDriver { - async fn resume_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result { - Self::resume_sandbox(self, sandbox_id, sandbox_name) +impl StartupSandboxStarter for DockerComputeDriver { + async fn start_sandbox(&self, sandbox_id: &str, sandbox_name: &str) -> Result { + Self::start_sandbox(self, sandbox_id, sandbox_name) .await .map_err(|err| err.to_string()) } @@ -524,13 +524,13 @@ impl ComputeDriver for RemoteComputeDriver { client.stop_sandbox(request).await } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> + request: Request, + ) -> Result, Status> { let mut client = self.client(); - client.resume_sandbox(request).await + client.start_sandbox(request).await } async fn delete_sandbox( @@ -558,7 +558,7 @@ pub struct ComputeRuntime { driver: TracedDriver, driver_info: ComputeDriverInfoSnapshot, shutdown_cleanup: Option>, - startup_resume: Option>, + startup_starter: Option>, driver_process: Option>, default_image: String, store: Arc, @@ -593,7 +593,7 @@ impl ComputeRuntime { driver_name: String, driver: SharedComputeDriver, shutdown_cleanup: Option>, - startup_resume: Option>, + startup_starter: Option>, driver_process: Option>, store: Arc, sandbox_index: SandboxIndex, @@ -679,7 +679,7 @@ impl ComputeRuntime { driver: TracedDriver::new(driver, driver_name), driver_info, shutdown_cleanup, - startup_resume, + startup_starter, driver_process, default_image, store, @@ -734,13 +734,13 @@ impl ComputeRuntime { .map_err(|err| ComputeError::Message(err.to_string()))?, ); let shutdown_cleanup: Arc = driver.clone(); - let startup_resume: Arc = driver.clone(); + let startup_starter: Arc = driver.clone(); let driver: SharedComputeDriver = driver; Self::from_driver( ComputeDriverKind::Docker.as_str().to_string(), driver, Some(shutdown_cleanup), - Some(startup_resume), + Some(startup_starter), None, store, sandbox_index, @@ -972,7 +972,7 @@ impl ComputeRuntime { } } - pub(crate) async fn suspend_sandbox( + pub(crate) async fn stop_sandbox( &self, workspace: &str, name: &str, @@ -995,57 +995,57 @@ impl ComputeRuntime { .ok_or_else(|| Status::not_found("sandbox not found"))?; if current.object_name() != sandbox_name { return Err(Status::aborted( - "sandbox name changed while the suspend request was waiting; retry explicitly", + "sandbox name changed while the stop request was waiting; retry explicitly", )); } let phase = SandboxPhase::try_from(current.phase()).unwrap_or(SandboxPhase::Unknown); - if phase == SandboxPhase::Suspended { - self.cleanup_suspended_sandbox_sessions(¤t) + if phase == SandboxPhase::Stopped { + self.cleanup_stopped_sandbox_sessions(¤t) .await .map_err(Status::internal)?; return Ok(current); } - if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Suspending) { + if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Stopping) { return Err(Status::failed_precondition(format!( - "sandbox must be Ready to suspend (current phase: {phase:?})" + "sandbox must be Ready to stop (current phase: {phase:?})" ))); } - let (previous, suspending) = if phase == SandboxPhase::Suspending { + let (previous, stopping) = if phase == SandboxPhase::Stopping { // Acquiring the lifecycle gate proves that no local worker still // owns this transition. Retry the idempotent driver operation. (current.clone(), current) } else { let previous = current.clone(); - let suspending = self + let stopping = self .write_lifecycle_phase( ¤t, - SandboxPhase::Suspending, - "Suspending", - "Sandbox suspension requested", + SandboxPhase::Stopping, + "Stopping", + "Sandbox stop requested", ) .await?; - self.sandbox_index.update_from_sandbox(&suspending); + self.sandbox_index.update_from_sandbox(&stopping); self.sandbox_watch_bus.notify(&sandbox_id); - (previous, suspending) + (previous, stopping) }; drop(global_guard); // Once the durable transition is committed, request cancellation must // not cancel the driver operation and strand the sandbox in - // `Suspending`. Keep the lifecycle gate in an owned worker, matching + // `Stopping`. Keep the lifecycle gate in an owned worker, matching // the delete path's cancellation semantics. let runtime = self.clone(); let request_span = tracing::Span::current(); tokio::spawn( async move { runtime - .complete_sandbox_suspend( + .complete_sandbox_stop( sandbox_id, sandbox_name, previous, - suspending, + stopping, lifecycle_guard, ) .await @@ -1055,17 +1055,17 @@ impl ComputeRuntime { .await .map_err(|err| { Status::internal(format!( - "sandbox suspend worker terminated unexpectedly: {err}" + "sandbox stop worker terminated unexpectedly: {err}" )) })? } - async fn complete_sandbox_suspend( + async fn complete_sandbox_stop( &self, sandbox_id: String, sandbox_name: String, previous: Sandbox, - suspending: Sandbox, + stopping: Sandbox, lifecycle_guard: SandboxLifecycleGuard, ) -> Result { let result = self @@ -1094,40 +1094,40 @@ impl ComputeRuntime { .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? .ok_or_else(|| Status::not_found("sandbox not found"))?; let phase = SandboxPhase::try_from(latest.phase()).unwrap_or(SandboxPhase::Unknown); - let suspended = if phase == SandboxPhase::Suspended { + let stopped = if phase == SandboxPhase::Stopped { latest - } else if phase == SandboxPhase::Suspending { + } else if phase == SandboxPhase::Stopping { self.write_lifecycle_phase( &latest, - SandboxPhase::Suspended, - "Suspended", + SandboxPhase::Stopped, + "Stopped", "Sandbox compute is stopped", ) .await? } else { return Err(Status::aborted( - "sandbox lifecycle changed while suspension completed", + "sandbox lifecycle changed while stop completed", )); }; - self.cleanup_suspended_sandbox_sessions(&suspended) + self.cleanup_stopped_sandbox_sessions(&stopped) .await .map_err(Status::internal)?; - self.sandbox_index.update_from_sandbox(&suspended); + self.sandbox_index.update_from_sandbox(&stopped); self.sandbox_watch_bus.notify(&sandbox_id); - Ok(suspended) + Ok(stopped) } Err(err) => { - self.recover_failed_lifecycle(&lifecycle_guard, &suspending, &previous, true) + self.recover_failed_lifecycle(&lifecycle_guard, &stopping, &previous, true) .await; Err(Status::new( err.code(), - format!("suspend sandbox failed: {}", err.message()), + format!("stop sandbox failed: {}", err.message()), )) } } } - pub(crate) async fn resume_sandbox( + pub(crate) async fn start_sandbox( &self, workspace: &str, name: &str, @@ -1150,7 +1150,7 @@ impl ComputeRuntime { .ok_or_else(|| Status::not_found("sandbox not found"))?; if current.object_name() != sandbox_name { return Err(Status::aborted( - "sandbox name changed while the resume request was waiting; retry explicitly", + "sandbox name changed while the start request was waiting; retry explicitly", )); } @@ -1158,44 +1158,44 @@ impl ComputeRuntime { if phase == SandboxPhase::Ready { return Ok(current); } - if !matches!(phase, SandboxPhase::Suspended | SandboxPhase::Resuming) { + if !matches!(phase, SandboxPhase::Stopped | SandboxPhase::Starting) { return Err(Status::failed_precondition(format!( - "sandbox must be Suspended to resume (current phase: {phase:?})" + "sandbox must be Stopped to start (current phase: {phase:?})" ))); } - let (previous, resuming) = if phase == SandboxPhase::Resuming { + let (previous, starting) = if phase == SandboxPhase::Starting { // Acquiring the lifecycle gate proves that no local worker still // owns this transition. Retry the idempotent driver operation. (current.clone(), current) } else { let previous = current.clone(); - let resuming = self + let starting = self .write_lifecycle_phase( ¤t, - SandboxPhase::Resuming, - "Resuming", - "Sandbox resume requested", + SandboxPhase::Starting, + "Starting", + "Sandbox start requested", ) .await?; - self.sandbox_index.update_from_sandbox(&resuming); + self.sandbox_index.update_from_sandbox(&starting); self.sandbox_watch_bus.notify(&sandbox_id); - (previous, resuming) + (previous, starting) }; drop(global_guard); - // The durable `Resuming` transition commits the operation. Let an + // The durable `Starting` transition commits the operation. Let an // owned worker finish it even if the initiating RPC is canceled. let runtime = self.clone(); let request_span = tracing::Span::current(); tokio::spawn( async move { runtime - .complete_sandbox_resume( + .complete_sandbox_start( sandbox_id, sandbox_name, previous, - resuming, + starting, lifecycle_guard, ) .await @@ -1205,27 +1205,27 @@ impl ComputeRuntime { .await .map_err(|err| { Status::internal(format!( - "sandbox resume worker terminated unexpectedly: {err}" + "sandbox start worker terminated unexpectedly: {err}" )) })? } - async fn complete_sandbox_resume( + async fn complete_sandbox_start( &self, sandbox_id: String, sandbox_name: String, previous: Sandbox, - resuming: Sandbox, + starting: Sandbox, lifecycle_guard: SandboxLifecycleGuard, ) -> Result { let result = self .driver - .call("driver.resume_sandbox", Some(&sandbox_id), |driver| { + .call("driver.start_sandbox", Some(&sandbox_id), |driver| { let sandbox_id = sandbox_id.clone(); let sandbox_name = sandbox_name.clone(); async move { driver - .resume_sandbox(Request::new(ResumeSandboxRequest { + .start_sandbox(Request::new(StartSandboxRequest { sandbox_id, sandbox_name, })) @@ -1246,11 +1246,11 @@ impl ComputeRuntime { Ok(latest) } Err(err) => { - self.recover_failed_lifecycle(&lifecycle_guard, &resuming, &previous, false) + self.recover_failed_lifecycle(&lifecycle_guard, &starting, &previous, false) .await; Err(Status::new( err.code(), - format!("resume sandbox failed: {}", err.message()), + format!("start sandbox failed: {}", err.message()), )) } } @@ -1277,29 +1277,29 @@ impl ComputeRuntime { match observed { Ok(Some(snapshot)) if snapshot.id == sandbox_id && snapshot.status.is_some() => { let backend_phase = derive_phase(snapshot.status.as_ref()); - let observed_stopped = backend_phase == SandboxPhase::Suspended + let observed_stopped = backend_phase == SandboxPhase::Stopped || driver_snapshot_confirms_stopped(&snapshot); let suspension_progressing = - expected_stopped && driver_snapshot_confirms_suspending(&snapshot); + expected_stopped && driver_snapshot_confirms_stopping(&snapshot); if suspension_progressing { - // The Kubernetes controller has accepted suspension and + // The Kubernetes controller has accepted the stop and // is waiting for its pod to terminate. Preserve the // durable transition so a later watch event can complete // it instead of claiming the sandbox is running again. - debug!(sandbox_id, "Sandbox suspension is still progressing"); + debug!(sandbox_id, "Sandbox stop is still progressing"); } else if backend_phase == SandboxPhase::Error || observed_stopped == expected_stopped { if let Some(reconciled) = self .reconcile_lifecycle_snapshot(transition, &snapshot) .await - && reconciled.phase() == SandboxPhase::Suspended as i32 - && let Err(err) = self.cleanup_suspended_sandbox_sessions(&reconciled).await + && reconciled.phase() == SandboxPhase::Stopped as i32 + && let Err(err) = self.cleanup_stopped_sandbox_sessions(&reconciled).await { warn!( sandbox_id, error = %err, - "Failed to clean up sessions after reconciling suspended sandbox" + "Failed to clean up sessions after reconciling stopped sandbox" ); } } else { @@ -2013,20 +2013,20 @@ impl ComputeRuntime { Ok(()) } - /// Resume sandboxes whose store records say they should be running. + /// Start sandboxes whose store records say they should be running. /// Drivers that do not auto-restart compute resources across gateway - /// restarts (currently only Docker) implement `StartupResume`. For + /// restarts (currently only Docker) implement `StartupSandboxStarter`. For /// each sandbox in the store whose phase is not `Deleting` or - /// `Error`, we ask the driver to resume the underlying resource. If + /// `Error`, we ask the driver to start the underlying resource. If /// the driver reports that the resource no longer exists or fails to /// start, the sandbox is moved to the `Error` phase so the failure /// surfaces in the UI. /// /// Should be called once at gateway startup, before watchers spawn, - /// so the watch loop sees the post-resume state on its first poll. - pub async fn resume_persisted_sandboxes(&self) -> Result<(), String> { + /// so the watch loop sees the post-start state on its first poll. + pub async fn start_persisted_sandboxes(&self) -> Result<(), String> { self.recover_persisted_lifecycle_transitions().await?; - let Some(resume) = &self.startup_resume else { + let Some(startup_hook) = &self.startup_starter else { return Ok(()); }; @@ -2036,7 +2036,7 @@ impl ComputeRuntime { .await .map_err(|e| e.to_string())?; - let mut resumed = 0usize; + let mut started = 0usize; let mut missing = 0usize; let mut failed = 0usize; @@ -2044,7 +2044,7 @@ impl ComputeRuntime { let sandbox = match Sandbox::decode(record.payload.as_slice()) { Ok(sandbox) => sandbox, Err(err) => { - warn!(error = %err, "Failed to decode sandbox record during startup resume"); + warn!(error = %err, "Failed to decode sandbox record during gateway startup"); continue; } }; @@ -2054,8 +2054,8 @@ impl ComputeRuntime { continue; } - match resume - .resume_sandbox(sandbox.object_id(), sandbox.object_name()) + match startup_hook + .start_sandbox(sandbox.object_id(), sandbox.object_name()) .await { Ok(true) => { @@ -2063,9 +2063,9 @@ impl ComputeRuntime { sandbox_id = %sandbox.object_id(), sandbox_name = %sandbox.object_name(), ?phase, - "Resumed sandbox during gateway startup" + "Started sandbox during gateway startup" ); - resumed += 1; + started += 1; } Ok(false) => { // Backend resource is gone but the store still @@ -2076,7 +2076,7 @@ impl ComputeRuntime { warn!( sandbox_id = %sandbox.object_id(), sandbox_name = %sandbox.object_name(), - "Cannot resume sandbox: backend resource is missing" + "Cannot start sandbox: backend resource is missing" ); self.mark_sandbox_error( &sandbox, @@ -2091,12 +2091,12 @@ impl ComputeRuntime { sandbox_id = %sandbox.object_id(), sandbox_name = %sandbox.object_name(), error = %err, - "Failed to resume sandbox during gateway startup" + "Failed to start sandbox during gateway startup" ); self.mark_sandbox_error( &sandbox, - "ResumeFailed", - &format!("Failed to resume sandbox during gateway startup: {err}"), + "StartFailed", + &format!("Failed to start sandbox during gateway startup: {err}"), ) .await; failed += 1; @@ -2104,12 +2104,12 @@ impl ComputeRuntime { } } - if resumed > 0 || missing > 0 || failed > 0 { + if started > 0 || missing > 0 || failed > 0 { info!( - resumed, + started, missing_backend = missing, failed, - "Sandbox resume sweep complete" + "Sandbox start sweep complete" ); } Ok(()) @@ -2131,12 +2131,12 @@ impl ComputeRuntime { }; let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); match phase { - SandboxPhase::Suspended => { - if let Err(err) = self.cleanup_suspended_sandbox_sessions(&sandbox).await { + SandboxPhase::Stopped => { + if let Err(err) = self.cleanup_stopped_sandbox_sessions(&sandbox).await { warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to complete recovered sandbox session cleanup"); } } - SandboxPhase::Suspending => { + SandboxPhase::Stopping => { let sandbox_id = sandbox.object_id().to_string(); let sandbox_name = sandbox.object_name().to_string(); let driver_sandbox_id = sandbox_id.clone(); @@ -2159,8 +2159,8 @@ impl ComputeRuntime { Ok(_) => match self .write_lifecycle_phase( &sandbox, - SandboxPhase::Suspended, - "Suspended", + SandboxPhase::Stopped, + "Stopped", "Sandbox compute is stopped", ) .await @@ -2169,32 +2169,32 @@ impl ComputeRuntime { self.sandbox_index.update_from_sandbox(&updated); self.sandbox_watch_bus.notify(updated.object_id()); if let Err(err) = - self.cleanup_suspended_sandbox_sessions(&updated).await + self.cleanup_stopped_sandbox_sessions(&updated).await { warn!(sandbox_id = %updated.object_id(), error = %err, "Failed to complete recovered sandbox session cleanup"); } } Err(err) => { - warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to persist recovered suspension"); + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to persist recovered stop"); } }, Err(err) => { - warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to recover sandbox suspension"); + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to recover sandbox stop"); } } } - SandboxPhase::Resuming => { + SandboxPhase::Starting => { let sandbox_id = sandbox.object_id().to_string(); let sandbox_name = sandbox.object_name().to_string(); let driver_sandbox_id = sandbox_id.clone(); if let Err(err) = self .driver .call( - "driver.resume_sandbox", + "driver.start_sandbox", Some(&sandbox_id), |driver| async move { driver - .resume_sandbox(Request::new(ResumeSandboxRequest { + .start_sandbox(Request::new(StartSandboxRequest { sandbox_id: driver_sandbox_id, sandbox_name, })) @@ -2203,7 +2203,7 @@ impl ComputeRuntime { ) .await { - warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to recover sandbox resume"); + warn!(sandbox_id = %sandbox.object_id(), error = %err, "Failed to recover sandbox start"); } } _ => {} @@ -2244,7 +2244,7 @@ impl ComputeRuntime { warn!( sandbox_id = %sandbox_id, error = %err, - "Failed to persist sandbox error state during startup resume" + "Failed to persist sandbox error state during gateway startup" ); } } @@ -2615,8 +2615,8 @@ impl ComputeRuntime { self.sandbox_index.update_from_sandbox(&sandbox); self.sandbox_watch_bus.notify(sandbox.object_id()); - if sandbox.phase() == SandboxPhase::Suspended as i32 { - self.cleanup_suspended_sandbox_sessions(&sandbox).await?; + if sandbox.phase() == SandboxPhase::Stopped as i32 { + self.cleanup_stopped_sandbox_sessions(&sandbox).await?; } Ok(()) } @@ -2650,8 +2650,8 @@ impl ComputeRuntime { current_phase, SandboxPhase::Deleting | SandboxPhase::Error - | SandboxPhase::Suspending - | SandboxPhase::Suspended + | SandboxPhase::Stopping + | SandboxPhase::Stopped ) { return Ok(()); } @@ -2788,11 +2788,11 @@ impl ComputeRuntime { Ok(()) } - async fn cleanup_suspended_sandbox_sessions(&self, sandbox: &Sandbox) -> Result<(), String> { + async fn cleanup_stopped_sandbox_sessions(&self, sandbox: &Sandbox) -> Result<(), String> { // Disconnect first so a store failure cannot leave the stopped // sandbox reachable through an existing supervisor stream. Both - // operations are idempotent and are retried for durable Suspended - // records during explicit suspend requests and startup recovery. + // operations are idempotent and are retried for durable Stopped + // records during explicit stop requests and startup recovery. self.supervisor_sessions.disconnect(sandbox.object_id()); self.cleanup_sandbox_ssh_sessions(sandbox.object_id(), sandbox.object_workspace()) .await @@ -2969,7 +2969,7 @@ impl ComputeRuntime { let phase = SandboxPhase::try_from(sandbox.phase()).unwrap_or(SandboxPhase::Unknown); if matches!( phase, - SandboxPhase::Suspending | SandboxPhase::Suspended | SandboxPhase::Resuming + SandboxPhase::Stopping | SandboxPhase::Stopped | SandboxPhase::Starting ) { let updated = self .store @@ -3440,18 +3440,18 @@ fn apply_driver_snapshot(sandbox: &mut Sandbox, incoming: &DriverSandbox, sessio ); phase = match old_phase { - SandboxPhase::Suspending - if phase == SandboxPhase::Suspended || driver_snapshot_confirms_stopped(incoming) => + SandboxPhase::Stopping + if phase == SandboxPhase::Stopped || driver_snapshot_confirms_stopped(incoming) => { - SandboxPhase::Suspended + SandboxPhase::Stopped } - SandboxPhase::Suspending if driver_snapshot_confirms_suspending(incoming) => { - SandboxPhase::Suspending + SandboxPhase::Stopping if driver_snapshot_confirms_stopping(incoming) => { + SandboxPhase::Stopping } - SandboxPhase::Suspending if phase != SandboxPhase::Error => SandboxPhase::Suspending, - SandboxPhase::Suspended => SandboxPhase::Suspended, - SandboxPhase::Resuming if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Error) => { - SandboxPhase::Resuming + SandboxPhase::Stopping if phase != SandboxPhase::Error => SandboxPhase::Stopping, + SandboxPhase::Stopped => SandboxPhase::Stopped, + SandboxPhase::Starting if !matches!(phase, SandboxPhase::Ready | SandboxPhase::Error) => { + SandboxPhase::Starting } _ => phase, }; @@ -3515,10 +3515,10 @@ fn driver_snapshot_confirms_stopped(incoming: &DriverSandbox) -> bool { }) } -fn driver_snapshot_confirms_suspending(incoming: &DriverSandbox) -> bool { +fn driver_snapshot_confirms_stopping(incoming: &DriverSandbox) -> bool { incoming.status.as_ref().is_some_and(|status| { status.conditions.iter().any(|condition| { - condition.r#type.eq_ignore_ascii_case("Suspended") + condition.r#type.eq_ignore_ascii_case("Stopped") && condition.status.eq_ignore_ascii_case("false") && matches!( condition.reason.to_ascii_lowercase().as_str(), @@ -3562,7 +3562,7 @@ impl ComposedPhase { // before this driver snapshot arrived. Keep Ready rather than letting a lagging // backend phase overwrite it. let phase = match backend_phase { - SandboxPhase::Error | SandboxPhase::Deleting | SandboxPhase::Suspended => backend_phase, + SandboxPhase::Error | SandboxPhase::Deleting | SandboxPhase::Stopped => backend_phase, _ if session_connected => SandboxPhase::Ready, _ => SandboxPhase::Provisioning, }; @@ -3666,10 +3666,10 @@ fn derive_phase(status: Option<&DriverSandboxStatus>) -> SandboxPhase { } if status.conditions.iter().any(|condition| { - condition.r#type.eq_ignore_ascii_case("Suspended") + condition.r#type.eq_ignore_ascii_case("Stopped") && condition.status.eq_ignore_ascii_case("true") }) { - return SandboxPhase::Suspended; + return SandboxPhase::Stopped; } for condition in &status.conditions { @@ -3725,7 +3725,7 @@ fn sandbox_phase_should_be_running(phase: SandboxPhase) -> bool { SandboxPhase::Unspecified | SandboxPhase::Provisioning | SandboxPhase::Ready - | SandboxPhase::Resuming + | SandboxPhase::Starting | SandboxPhase::Unknown ) } @@ -3829,13 +3829,13 @@ impl ComputeDriver for NoopTestDriver { )) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: Request, - ) -> Result, Status> + _request: Request, + ) -> Result, Status> { Ok(tonic::Response::new( - openshell_core::proto::compute::v1::ResumeSandboxResponse {}, + openshell_core::proto::compute::v1::StartSandboxResponse {}, )) } @@ -3872,7 +3872,7 @@ pub async fn new_test_runtime_for_driver(store: Arc, driver_name: &str) - driver_version: "test".to_string(), }, shutdown_cleanup: None, - startup_resume: None, + startup_starter: None, driver_process: None, default_image: "openshell/sandbox:test".to_string(), store, @@ -3893,7 +3893,7 @@ mod tests { use futures::stream; use openshell_core::proto::compute::v1::{ CreateSandboxResponse, DeleteSandboxResponse, GetCapabilitiesResponse, GetSandboxRequest, - GetSandboxResponse, ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, + GetSandboxResponse, StartSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateResponse, WatchSandboxesDeletedEvent, WatchSandboxesSandboxEvent, }; use std::collections::HashMap; @@ -4110,11 +4110,11 @@ mod tests { Ok(tonic::Response::new(StopSandboxResponse {})) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: Request, - ) -> Result, Status> { - Ok(tonic::Response::new(ResumeSandboxResponse {})) + _request: Request, + ) -> Result, Status> { + Ok(tonic::Response::new(StartSandboxResponse {})) } async fn delete_sandbox( @@ -4168,12 +4168,12 @@ mod tests { stop_blocked: AtomicBool, stop_calls: AtomicUsize, stop_outcome: TestMutex, - resume_started: Notify, - resume_finished: Notify, - resume_release: Semaphore, - resume_blocked: AtomicBool, - resume_calls: AtomicUsize, - resume_outcome: TestMutex, + start_started: Notify, + start_finished: Notify, + start_release: Semaphore, + start_blocked: AtomicBool, + start_calls: AtomicUsize, + start_outcome: TestMutex, get_started: Notify, get_release: Semaphore, get_blocked: AtomicBool, @@ -4198,12 +4198,12 @@ mod tests { stop_blocked: AtomicBool::new(false), stop_calls: AtomicUsize::new(0), stop_outcome: TestMutex::new(ControlledLifecycleOutcome::Ok), - resume_started: Notify::new(), - resume_finished: Notify::new(), - resume_release: Semaphore::new(0), - resume_blocked: AtomicBool::new(false), - resume_calls: AtomicUsize::new(0), - resume_outcome: TestMutex::new(ControlledLifecycleOutcome::Ok), + start_started: Notify::new(), + start_finished: Notify::new(), + start_release: Semaphore::new(0), + start_blocked: AtomicBool::new(false), + start_calls: AtomicUsize::new(0), + start_outcome: TestMutex::new(ControlledLifecycleOutcome::Ok), get_started: Notify::new(), get_release: Semaphore::new(0), get_blocked: AtomicBool::new(false), @@ -4227,12 +4227,12 @@ mod tests { self.stop_release.add_permits(1); } - fn block_resume(&self) { - self.resume_blocked.store(true, Ordering::SeqCst); + fn block_start(&self) { + self.start_blocked.store(true, Ordering::SeqCst); } - fn release_resume(&self) { - self.resume_release.add_permits(1); + fn release_start(&self) { + self.start_release.add_permits(1); } fn block_get(&self) { @@ -4257,11 +4257,11 @@ mod tests { .expect("stop outcome lock poisoned") = outcome; } - fn set_resume_outcome(&self, outcome: ControlledLifecycleOutcome) { + fn set_start_outcome(&self, outcome: ControlledLifecycleOutcome) { *self - .resume_outcome + .start_outcome .lock() - .expect("resume outcome lock poisoned") = outcome; + .expect("start outcome lock poisoned") = outcome; } fn set_get_outcome(&self, outcome: ControlledGetOutcome) { @@ -4276,8 +4276,8 @@ mod tests { self.stop_calls.load(Ordering::SeqCst) } - fn resume_calls(&self) -> usize { - self.resume_calls.load(Ordering::SeqCst) + fn start_calls(&self) -> usize { + self.start_calls.load(Ordering::SeqCst) } fn send_event(&self, event: WatchSandboxesEvent) { @@ -4392,29 +4392,27 @@ mod tests { } } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: Request, - ) -> Result, Status> { - self.resume_calls.fetch_add(1, Ordering::SeqCst); - self.resume_started.notify_one(); - if self.resume_blocked.load(Ordering::SeqCst) { - self.resume_release + _request: Request, + ) -> Result, Status> { + self.start_calls.fetch_add(1, Ordering::SeqCst); + self.start_started.notify_one(); + if self.start_blocked.load(Ordering::SeqCst) { + self.start_release .acquire() .await - .expect("resume release semaphore closed") + .expect("start release semaphore closed") .forget(); } - self.resume_finished.notify_one(); + self.start_finished.notify_one(); let outcome = self - .resume_outcome + .start_outcome .lock() - .expect("resume outcome lock poisoned") + .expect("start outcome lock poisoned") .clone(); match outcome { - ControlledLifecycleOutcome::Ok => { - Ok(tonic::Response::new(ResumeSandboxResponse {})) - } + ControlledLifecycleOutcome::Ok => Ok(tonic::Response::new(StartSandboxResponse {})), ControlledLifecycleOutcome::Error(message) => Err(Status::internal(message)), } } @@ -4463,12 +4461,12 @@ mod tests { } async fn test_runtime(driver: SharedComputeDriver) -> ComputeRuntime { - test_runtime_with_resume(driver, None).await + test_runtime_with_start(driver, None).await } - async fn test_runtime_with_resume( + async fn test_runtime_with_start( driver: SharedComputeDriver, - startup_resume: Option>, + startup_starter: Option>, ) -> ComputeRuntime { let store = Arc::new(Store::connect("sqlite::memory:").await.unwrap()); ComputeRuntime { @@ -4479,7 +4477,7 @@ mod tests { driver_version: "test".to_string(), }, shutdown_cleanup: None, - startup_resume, + startup_starter, driver_process: None, default_image: "openshell/sandbox:test".to_string(), store, @@ -5206,11 +5204,11 @@ mod tests { self.0.stop_sandbox(request).await } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> { - self.0.resume_sandbox(request).await + request: Request, + ) -> Result, Status> { + self.0.start_sandbox(request).await } async fn delete_sandbox( @@ -5259,7 +5257,7 @@ mod tests { } #[tokio::test] - async fn suspend_and_resume_follow_durable_state_machine() { + async fn stop_and_start_follow_durable_state_machine() { let driver = ControlledDriver::new(); let runtime = test_runtime(driver.clone()).await; let sandbox = sandbox_record("sb-lifecycle", "sandbox-lifecycle", SandboxPhase::Ready); @@ -5268,11 +5266,11 @@ mod tests { runtime.store.put_message(&session).await.unwrap(); register_test_supervisor_session(&runtime, sandbox.object_id()); - let suspended = runtime - .suspend_sandbox("default", sandbox.object_name()) + let stopped = runtime + .stop_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(suspended.phase(), SandboxPhase::Suspended as i32); + assert_eq!(stopped.phase(), SandboxPhase::Stopped as i32); assert_eq!(driver.stop_calls(), 1); assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); assert!( @@ -5282,32 +5280,32 @@ mod tests { .await .unwrap() .is_none(), - "suspension revokes ephemeral SSH sessions" + "stop revokes ephemeral SSH sessions" ); - let suspended_again = runtime - .suspend_sandbox("default", sandbox.object_name()) + let stopped_again = runtime + .stop_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(suspended_again.phase(), SandboxPhase::Suspended as i32); - assert_eq!(driver.stop_calls(), 1, "stable suspension is idempotent"); + assert_eq!(stopped_again.phase(), SandboxPhase::Stopped as i32); + assert_eq!(driver.stop_calls(), 1, "stable stop is idempotent"); - let resuming = runtime - .resume_sandbox("default", sandbox.object_name()) + let starting = runtime + .start_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); - assert_eq!(driver.resume_calls(), 1); + assert_eq!(starting.phase(), SandboxPhase::Starting as i32); + assert_eq!(driver.start_calls(), 1); - let resuming_again = runtime - .resume_sandbox("default", sandbox.object_name()) + let starting_again = runtime + .start_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(resuming_again.phase(), SandboxPhase::Resuming as i32); + assert_eq!(starting_again.phase(), SandboxPhase::Starting as i32); assert_eq!( - driver.resume_calls(), + driver.start_calls(), 2, - "explicit retry reissues the idempotent resume" + "explicit retry reissues the idempotent start" ); register_test_supervisor_session(&runtime, sandbox.object_id()); @@ -5319,69 +5317,69 @@ mod tests { .await .unwrap(); let ready = runtime - .resume_sandbox("default", sandbox.object_name()) + .start_sandbox("default", sandbox.object_name()) .await .unwrap(); assert_eq!(ready.phase(), SandboxPhase::Ready as i32); - assert_eq!(driver.resume_calls(), 2, "ready resume is idempotent"); + assert_eq!(driver.start_calls(), 2, "ready start is idempotent"); } #[tokio::test] - async fn retained_suspending_transition_retries_driver_operation() { + async fn retained_stopping_transition_retries_driver_operation() { let driver = ControlledDriver::new(); let runtime = test_runtime(driver.clone()).await; let sandbox = sandbox_record( - "sb-retained-suspend", - "sandbox-retained-suspend", - SandboxPhase::Suspending, + "sb-retained-stop", + "sandbox-retained-stop", + SandboxPhase::Stopping, ); runtime.store.put_message(&sandbox).await.unwrap(); - let suspended = runtime - .suspend_sandbox("default", sandbox.object_name()) + let stopped = runtime + .stop_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(suspended.phase(), SandboxPhase::Suspended as i32); + assert_eq!(stopped.phase(), SandboxPhase::Stopped as i32); assert_eq!(driver.stop_calls(), 1); } #[tokio::test] - async fn retained_resuming_transition_retries_driver_operation() { + async fn retained_starting_transition_retries_driver_operation() { let driver = ControlledDriver::new(); let runtime = test_runtime(driver.clone()).await; let sandbox = sandbox_record( - "sb-retained-resume", - "sandbox-retained-resume", - SandboxPhase::Resuming, + "sb-retained-start", + "sandbox-retained-start", + SandboxPhase::Starting, ); runtime.store.put_message(&sandbox).await.unwrap(); - let resuming = runtime - .resume_sandbox("default", sandbox.object_name()) + let starting = runtime + .start_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); - assert_eq!(driver.resume_calls(), 1); + assert_eq!(starting.phase(), SandboxPhase::Starting as i32); + assert_eq!(driver.start_calls(), 1); } #[tokio::test] - async fn repeated_suspend_completes_session_cleanup() { + async fn repeated_stop_completes_session_cleanup() { let driver = ControlledDriver::new(); let runtime = test_runtime(driver.clone()).await; - let sandbox = sandbox_record("sb-suspended", "sandbox-suspended", SandboxPhase::Suspended); + let sandbox = sandbox_record("sb-stopped", "sandbox-stopped", SandboxPhase::Stopped); let session = ssh_session_record("stale-session", sandbox.object_id()); runtime.store.put_message(&sandbox).await.unwrap(); runtime.store.put_message(&session).await.unwrap(); register_test_supervisor_session(&runtime, sandbox.object_id()); - let suspended = runtime - .suspend_sandbox("default", sandbox.object_name()) + let stopped = runtime + .stop_sandbox("default", sandbox.object_name()) .await .unwrap(); - assert_eq!(suspended.phase(), SandboxPhase::Suspended as i32); + assert_eq!(stopped.phase(), SandboxPhase::Stopped as i32); assert_eq!(driver.stop_calls(), 0); assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); assert!( @@ -5395,40 +5393,32 @@ mod tests { } #[tokio::test] - async fn startup_recovery_completes_suspended_session_cleanup() { + async fn startup_recovery_completes_stopped_session_cleanup() { let driver = ControlledDriver::new(); let runtime = test_runtime(driver.clone()).await; - let suspended = - sandbox_record("sb-suspended", "sandbox-suspended", SandboxPhase::Suspended); - let suspending = sandbox_record( - "sb-suspending", - "sandbox-suspending", - SandboxPhase::Suspending, - ); - let suspended_session = ssh_session_record("suspended-session", suspended.object_id()); - let suspending_session = ssh_session_record("suspending-session", suspending.object_id()); - for sandbox in [&suspended, &suspending] { + let stopped = sandbox_record("sb-stopped", "sandbox-stopped", SandboxPhase::Stopped); + let stopping = sandbox_record("sb-stopping", "sandbox-stopping", SandboxPhase::Stopping); + let stopped_session = ssh_session_record("stopped-session", stopped.object_id()); + let stopping_session = ssh_session_record("stopping-session", stopping.object_id()); + for sandbox in [&stopped, &stopping] { runtime.store.put_message(sandbox).await.unwrap(); register_test_supervisor_session(&runtime, sandbox.object_id()); } - for session in [&suspended_session, &suspending_session] { + for session in [&stopped_session, &stopping_session] { runtime.store.put_message(session).await.unwrap(); } - runtime.resume_persisted_sandboxes().await.unwrap(); + runtime.start_persisted_sandboxes().await.unwrap(); assert_eq!(driver.stop_calls(), 1); - for (sandbox, session) in [ - (&suspended, &suspended_session), - (&suspending, &suspending_session), - ] { + for (sandbox, session) in [(&stopped, &stopped_session), (&stopping, &stopping_session)] { let stored = runtime .store .get_message::(sandbox.object_id()) .await .unwrap() .unwrap(); - assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + assert_eq!(stored.phase(), SandboxPhase::Stopped as i32); assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); assert!( runtime @@ -5442,29 +5432,29 @@ mod tests { } #[tokio::test] - async fn request_cancellation_does_not_cancel_suspend_worker() { + async fn request_cancellation_does_not_cancel_stop_worker() { let driver = ControlledDriver::new(); driver.block_stop(); let runtime = test_runtime(driver.clone()).await; - let sandbox = sandbox_record("sb-suspend", "sandbox-suspend", SandboxPhase::Ready); + let sandbox = sandbox_record("sb-stop", "sandbox-stop", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); let request_runtime = runtime.clone(); let request = tokio::spawn(async move { request_runtime - .suspend_sandbox("default", "sandbox-suspend") + .stop_sandbox("default", "sandbox-stop") .await }); tokio::time::timeout(Duration::from_secs(1), driver.stop_started.notified()) .await - .expect("suspend did not reach the driver"); + .expect("stop did not reach the driver"); request.abort(); assert!(request.await.unwrap_err().is_cancelled()); driver.release_stop(); tokio::time::timeout(Duration::from_secs(1), driver.stop_finished.notified()) .await - .expect("detached suspend worker did not finish the driver call"); + .expect("detached stop worker did not finish the driver call"); tokio::time::timeout(Duration::from_secs(1), async { loop { @@ -5474,59 +5464,59 @@ mod tests { .await .unwrap() .unwrap(); - if stored.phase() == SandboxPhase::Suspended as i32 { + if stored.phase() == SandboxPhase::Stopped as i32 { break; } tokio::task::yield_now().await; } }) .await - .expect("detached suspend worker did not persist Suspended"); + .expect("detached stop worker did not persist Stopped"); assert_eq!(driver.stop_calls(), 1); } #[tokio::test] - async fn request_cancellation_does_not_cancel_resume_worker() { + async fn request_cancellation_does_not_cancel_start_worker() { let driver = ControlledDriver::new(); - driver.block_resume(); + driver.block_start(); let runtime = test_runtime(driver.clone()).await; - let sandbox = sandbox_record("sb-resume", "sandbox-resume", SandboxPhase::Suspended); + let sandbox = sandbox_record("sb-start", "sandbox-start", SandboxPhase::Stopped); runtime.store.put_message(&sandbox).await.unwrap(); let request_runtime = runtime.clone(); let request = tokio::spawn(async move { request_runtime - .resume_sandbox("default", "sandbox-resume") + .start_sandbox("default", "sandbox-start") .await }); - tokio::time::timeout(Duration::from_secs(1), driver.resume_started.notified()) + tokio::time::timeout(Duration::from_secs(1), driver.start_started.notified()) .await - .expect("resume did not reach the driver"); + .expect("start did not reach the driver"); request.abort(); assert!(request.await.unwrap_err().is_cancelled()); - driver.release_resume(); - tokio::time::timeout(Duration::from_secs(1), driver.resume_finished.notified()) + driver.release_start(); + tokio::time::timeout(Duration::from_secs(1), driver.start_finished.notified()) .await - .expect("detached resume worker did not finish the driver call"); + .expect("detached start worker did not finish the driver call"); - driver.release_resume(); - let resuming = tokio::time::timeout( + driver.release_start(); + let starting = tokio::time::timeout( Duration::from_secs(1), - runtime.resume_sandbox("default", sandbox.object_name()), + runtime.start_sandbox("default", sandbox.object_name()), ) .await - .expect("detached resume worker did not release the lifecycle gate") + .expect("detached start worker did not release the lifecycle gate") .unwrap(); - assert_eq!(resuming.phase(), SandboxPhase::Resuming as i32); - assert_eq!(driver.resume_calls(), 2); + assert_eq!(starting.phase(), SandboxPhase::Starting as i32); + assert_eq!(driver.start_calls(), 2); } #[tokio::test] - async fn failed_suspend_reconciles_backend_that_already_stopped() { + async fn failed_stop_reconciles_backend_that_already_stopped() { let driver = ControlledDriver::new(); driver.set_stop_outcome(ControlledLifecycleOutcome::Error("response lost")); - let sandbox = sandbox_record("sb-suspend", "sandbox-suspend", SandboxPhase::Ready); + let sandbox = sandbox_record("sb-stop", "sandbox-stop", SandboxPhase::Ready); let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); stopped.status = Some(make_driver_status(make_driver_condition( "ContainerExited", @@ -5540,7 +5530,7 @@ mod tests { register_test_supervisor_session(&runtime, sandbox.object_id()); let err = runtime - .suspend_sandbox("default", sandbox.object_name()) + .stop_sandbox("default", sandbox.object_name()) .await .unwrap_err(); assert!(err.message().contains("response lost")); @@ -5551,7 +5541,7 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + assert_eq!(stored.phase(), SandboxPhase::Stopped as i32); assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); assert!( runtime @@ -5560,17 +5550,17 @@ mod tests { .await .unwrap() .is_none(), - "reconciled suspension revokes ephemeral SSH sessions" + "reconciled stop revokes ephemeral SSH sessions" ); } #[tokio::test] - async fn failed_suspend_retains_progress_and_watcher_completes_cleanup() { + async fn failed_stop_retains_progress_and_watcher_completes_cleanup() { let driver = ControlledDriver::new(); - driver.set_stop_outcome(ControlledLifecycleOutcome::Error("suspend timed out")); + driver.set_stop_outcome(ControlledLifecycleOutcome::Error("stop timed out")); let sandbox = sandbox_record( - "sb-suspend-progressing", - "sandbox-suspend-progressing", + "sb-stop-progressing", + "sandbox-stop-progressing", SandboxPhase::Ready, ); let mut progressing = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); @@ -5579,13 +5569,13 @@ mod tests { instance_id: format!("{}-pod", sandbox.object_name()), conditions: vec![ DriverCondition { - r#type: "Suspended".to_string(), + r#type: "Stopped".to_string(), status: "False".to_string(), reason: "PodTerminating".to_string(), - message: "Pod is terminating. Sandbox is suspending".to_string(), + message: "Pod is terminating. Sandbox is stopping".to_string(), last_transition_time: String::new(), }, - make_driver_condition("SandboxSuspended", "Sandbox is suspending"), + make_driver_condition("SandboxStopped", "Sandbox is stopping"), ], ..Default::default() }); @@ -5597,10 +5587,10 @@ mod tests { register_test_supervisor_session(&runtime, sandbox.object_id()); let err = runtime - .suspend_sandbox("default", sandbox.object_name()) + .stop_sandbox("default", sandbox.object_name()) .await .unwrap_err(); - assert!(err.message().contains("suspend timed out")); + assert!(err.message().contains("stop timed out")); let stored = runtime .store @@ -5608,7 +5598,7 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(stored.phase(), SandboxPhase::Suspending as i32); + assert_eq!(stored.phase(), SandboxPhase::Stopping as i32); assert!(runtime.supervisor_sessions.has_session(sandbox.object_id())); progressing.status.as_mut().unwrap().conditions[0].status = "True".to_string(); @@ -5621,7 +5611,7 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(stored.phase(), SandboxPhase::Suspended as i32); + assert_eq!(stored.phase(), SandboxPhase::Stopped as i32); assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); assert!( runtime @@ -5630,15 +5620,15 @@ mod tests { .await .unwrap() .is_none(), - "watcher-driven suspension revokes ephemeral SSH sessions" + "watcher-driven stop revokes ephemeral SSH sessions" ); } #[tokio::test] - async fn failed_resume_reconciles_backend_that_already_started() { + async fn failed_start_reconciles_backend_that_already_started() { let driver = ControlledDriver::new(); - driver.set_resume_outcome(ControlledLifecycleOutcome::Error("response lost")); - let sandbox = sandbox_record("sb-resume", "sandbox-resume", SandboxPhase::Suspended); + driver.set_start_outcome(ControlledLifecycleOutcome::Error("response lost")); + let sandbox = sandbox_record("sb-start", "sandbox-start", SandboxPhase::Stopped); driver.set_get_outcome(ControlledGetOutcome::Sandbox(Box::new( ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()), ))); @@ -5646,7 +5636,7 @@ mod tests { runtime.store.put_message(&sandbox).await.unwrap(); let err = runtime - .resume_sandbox("default", sandbox.object_name()) + .start_sandbox("default", sandbox.object_name()) .await .unwrap_err(); assert!(err.message().contains("response lost")); @@ -5657,7 +5647,7 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(stored.phase(), SandboxPhase::Resuming as i32); + assert_eq!(stored.phase(), SandboxPhase::Starting as i32); } #[tokio::test] @@ -5670,23 +5660,23 @@ mod tests { ); runtime.store.put_message(&sandbox).await.unwrap(); - let suspend = runtime - .suspend_sandbox("default", sandbox.object_name()) + let stop = runtime + .stop_sandbox("default", sandbox.object_name()) .await .unwrap_err(); - assert_eq!(suspend.code(), Code::FailedPrecondition); + assert_eq!(stop.code(), Code::FailedPrecondition); - let resume = runtime - .resume_sandbox("default", sandbox.object_name()) + let start = runtime + .start_sandbox("default", sandbox.object_name()) .await .unwrap_err(); - assert_eq!(resume.code(), Code::FailedPrecondition); + assert_eq!(start.code(), Code::FailedPrecondition); } #[tokio::test] - async fn stale_ready_snapshot_cannot_wake_suspended_sandbox() { + async fn stale_ready_snapshot_cannot_wake_stopped_sandbox() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; - let sandbox = sandbox_record("sb-sleeping", "sandbox-sleeping", SandboxPhase::Suspended); + let sandbox = sandbox_record("sb-sleeping", "sandbox-sleeping", SandboxPhase::Stopped); runtime.store.put_message(&sandbox).await.unwrap(); register_test_supervisor_session(&runtime, sandbox.object_id()); @@ -5704,22 +5694,18 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(current.phase(), SandboxPhase::Suspended as i32); + assert_eq!(current.phase(), SandboxPhase::Stopped as i32); } #[tokio::test] - async fn stopped_container_snapshot_confirms_suspending_sandbox() { + async fn stopped_container_snapshot_confirms_stopping_sandbox() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; - let sandbox = sandbox_record( - "sb-suspending", - "sandbox-suspending", - SandboxPhase::Suspending, - ); + let sandbox = sandbox_record("sb-stopping", "sandbox-stopping", SandboxPhase::Stopping); runtime.store.put_message(&sandbox).await.unwrap(); let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); stopped.status = Some(make_driver_status(make_driver_condition( "ContainerExited", - "container stopped for suspension", + "container stopped by request", ))); runtime.apply_sandbox_update(stopped).await.unwrap(); @@ -5730,13 +5716,13 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(current.phase(), SandboxPhase::Suspended as i32); + assert_eq!(current.phase(), SandboxPhase::Stopped as i32); } #[tokio::test] - async fn stopped_container_snapshot_cannot_error_suspended_sandbox() { + async fn stopped_container_snapshot_cannot_error_stopped_sandbox() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; - let sandbox = sandbox_record("sb-suspended", "sandbox-suspended", SandboxPhase::Suspended); + let sandbox = sandbox_record("sb-stopped", "sandbox-stopped", SandboxPhase::Stopped); runtime.store.put_message(&sandbox).await.unwrap(); let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); stopped.status = Some(make_driver_status(make_driver_condition( @@ -5752,7 +5738,7 @@ mod tests { .await .unwrap() .unwrap(); - assert_eq!(current.phase(), SandboxPhase::Suspended as i32); + assert_eq!(current.phase(), SandboxPhase::Stopped as i32); } #[tokio::test] @@ -7820,12 +7806,12 @@ mod tests { } #[derive(Default)] - struct RecordingResume { + struct RecordingStart { calls: Mutex>, results: Mutex>>, } - impl RecordingResume { + impl RecordingStart { async fn set_result(&self, sandbox_id: &str, result: Result) { self.results .lock() @@ -7839,8 +7825,8 @@ mod tests { } #[tonic::async_trait] - impl StartupResume for RecordingResume { - async fn resume_sandbox( + impl StartupSandboxStarter for RecordingStart { + async fn start_sandbox( &self, sandbox_id: &str, sandbox_name: &str, @@ -7859,10 +7845,10 @@ mod tests { } #[tokio::test] - async fn resume_persisted_sandboxes_resumes_running_phases() { - let resume = Arc::new(RecordingResume::default()); + async fn start_persisted_sandboxes_starts_running_phases() { + let start = Arc::new(RecordingStart::default()); let runtime = - test_runtime_with_resume(Arc::new(TestDriver::default()), Some(resume.clone())).await; + test_runtime_with_start(Arc::new(TestDriver::default()), Some(start.clone())).await; for (id, name, phase) in [ ("sb-unspecified", "unspecified", SandboxPhase::Unspecified), @@ -7876,9 +7862,9 @@ mod tests { runtime.store.put_message(&sandbox).await.unwrap(); } - runtime.resume_persisted_sandboxes().await.unwrap(); + runtime.start_persisted_sandboxes().await.unwrap(); - let mut called_ids = resume + let mut called_ids = start .calls() .await .into_iter() @@ -7897,16 +7883,16 @@ mod tests { } #[tokio::test] - async fn resume_persisted_sandboxes_marks_missing_backend_as_error() { - let resume = Arc::new(RecordingResume::default()); - resume.set_result("sb-1", Ok(false)).await; + async fn start_persisted_sandboxes_marks_missing_backend_as_error() { + let start = Arc::new(RecordingStart::default()); + start.set_result("sb-1", Ok(false)).await; let runtime = - test_runtime_with_resume(Arc::new(TestDriver::default()), Some(resume.clone())).await; + test_runtime_with_start(Arc::new(TestDriver::default()), Some(start.clone())).await; let sandbox = sandbox_record("sb-1", "missing", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); - runtime.resume_persisted_sandboxes().await.unwrap(); + runtime.start_persisted_sandboxes().await.unwrap(); let stored = runtime .store @@ -7927,18 +7913,18 @@ mod tests { } #[tokio::test] - async fn resume_persisted_sandboxes_marks_failed_resume_as_error() { - let resume = Arc::new(RecordingResume::default()); - resume + async fn start_persisted_sandboxes_marks_failed_start_as_error() { + let start = Arc::new(RecordingStart::default()); + start .set_result("sb-1", Err("docker daemon angry".to_string())) .await; let runtime = - test_runtime_with_resume(Arc::new(TestDriver::default()), Some(resume.clone())).await; + test_runtime_with_start(Arc::new(TestDriver::default()), Some(start.clone())).await; let sandbox = sandbox_record("sb-1", "broken", SandboxPhase::Provisioning); runtime.store.put_message(&sandbox).await.unwrap(); - runtime.resume_persisted_sandboxes().await.unwrap(); + runtime.start_persisted_sandboxes().await.unwrap(); let stored = runtime .store @@ -7955,17 +7941,17 @@ mod tests { .as_ref() .and_then(|s| s.conditions.iter().find(|c| c.r#type == "Ready")) .expect("Ready condition present"); - assert_eq!(ready.reason, "ResumeFailed"); + assert_eq!(ready.reason, "StartFailed"); assert!(ready.message.contains("docker daemon angry")); } #[tokio::test] - async fn resume_persisted_sandboxes_is_noop_without_resume_hook() { + async fn start_persisted_sandboxes_is_noop_without_start_hook() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; let sandbox = sandbox_record("sb-1", "anywhere", SandboxPhase::Ready); runtime.store.put_message(&sandbox).await.unwrap(); - runtime.resume_persisted_sandboxes().await.unwrap(); + runtime.start_persisted_sandboxes().await.unwrap(); let stored = runtime .store diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 5339352c57..a0ea195442 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -44,11 +44,11 @@ use openshell_core::proto::{ RefreshSandboxTokenRequest, RefreshSandboxTokenResponse, RejectDraftChunkRequest, RejectDraftChunkResponse, RelayFrame, RemoveWorkspaceMemberRequest, RemoveWorkspaceMemberResponse, ReportPolicyStatusRequest, ReportPolicyStatusResponse, - ResumeSandboxRequest, RevokeSshSessionRequest, RevokeSshSessionResponse, - RotateProviderCredentialRequest, RotateProviderCredentialResponse, SandboxResponse, - ServiceEndpointResponse, ServiceStatus, SubmitPolicyAnalysisRequest, - SubmitPolicyAnalysisResponse, SupervisorMessage, SuspendSandboxRequest, TcpForwardFrame, - UndoDraftChunkRequest, UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, + RevokeSshSessionRequest, RevokeSshSessionResponse, RotateProviderCredentialRequest, + RotateProviderCredentialResponse, SandboxResponse, ServiceEndpointResponse, ServiceStatus, + StartSandboxRequest, StopSandboxRequest, SubmitPolicyAnalysisRequest, + SubmitPolicyAnalysisResponse, SupervisorMessage, TcpForwardFrame, UndoDraftChunkRequest, + UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, UpdateProviderProfilesRequest, UpdateProviderProfilesResponse, UpdateProviderRequest, WatchSandboxRequest, open_shell_server::OpenShell, }; @@ -324,18 +324,18 @@ impl OpenShell for OpenShellService { sandbox::handle_delete_sandbox(&self.state, request).await } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - request: Request, + request: Request, ) -> Result, Status> { - sandbox::handle_suspend_sandbox(&self.state, request).await + sandbox::handle_stop_sandbox(&self.state, request).await } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, + request: Request, ) -> Result, Status> { - sandbox::handle_resume_sandbox(&self.state, request).await + sandbox::handle_start_sandbox(&self.state, request).await } // --- Exec --- diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index d8d707a061..9338956950 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -22,10 +22,10 @@ use openshell_core::proto::{ DetachSandboxProviderRequest, DetachSandboxProviderResponse, ExecSandboxEvent, ExecSandboxExit, ExecSandboxInput, ExecSandboxRequest, ExecSandboxStderr, ExecSandboxStdout, GetSandboxRequest, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxesRequest, - ListSandboxesResponse, Provider, ResumeSandboxRequest, RevokeSshSessionRequest, - RevokeSshSessionResponse, SandboxResponse, SandboxStreamEvent, SshRelayTarget, - SuspendSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, WatchSandboxRequest, - relay_open, tcp_forward_init, + ListSandboxesResponse, Provider, RevokeSshSessionRequest, RevokeSshSessionResponse, + SandboxResponse, SandboxStreamEvent, SshRelayTarget, StartSandboxRequest, StopSandboxRequest, + TcpForwardFrame, TcpForwardInit, TcpRelayTarget, WatchSandboxRequest, relay_open, + tcp_forward_init, }; use openshell_core::proto::{Sandbox, SandboxPhase, SandboxTemplate, SshSession}; use openshell_core::telemetry::{ @@ -764,14 +764,14 @@ async fn handle_delete_sandbox_inner( })) } -pub(super) async fn handle_suspend_sandbox( +pub(super) async fn handle_stop_sandbox( state: &Arc, - request: Request, + request: Request, ) -> Result, Status> { - let result = handle_suspend_sandbox_inner(state, request).await; + let result = handle_stop_sandbox_inner(state, request).await; openshell_core::telemetry::emit_lifecycle( LifecycleResource::Sandbox, - LifecycleOperation::Suspend, + LifecycleOperation::Stop, if result.is_ok() { TelemetryOutcome::Success } else { @@ -781,9 +781,9 @@ pub(super) async fn handle_suspend_sandbox( result } -async fn handle_suspend_sandbox_inner( +async fn handle_stop_sandbox_inner( state: &Arc, - request: Request, + request: Request, ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); @@ -801,21 +801,21 @@ async fn handle_suspend_sandbox_inner( let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) .await? .name; - let sandbox = state.compute.suspend_sandbox(&workspace, &req.name).await?; - info!(sandbox_name = %req.name, "SuspendSandbox request completed successfully"); + let sandbox = state.compute.stop_sandbox(&workspace, &req.name).await?; + info!(sandbox_name = %req.name, "StopSandbox request completed successfully"); Ok(Response::new(SandboxResponse { sandbox: Some(sandbox), })) } -pub(super) async fn handle_resume_sandbox( +pub(super) async fn handle_start_sandbox( state: &Arc, - request: Request, + request: Request, ) -> Result, Status> { - let result = handle_resume_sandbox_inner(state, request).await; + let result = handle_start_sandbox_inner(state, request).await; openshell_core::telemetry::emit_lifecycle( LifecycleResource::Sandbox, - LifecycleOperation::Resume, + LifecycleOperation::Start, if result.is_ok() { TelemetryOutcome::Success } else { @@ -825,9 +825,9 @@ pub(super) async fn handle_resume_sandbox( result } -async fn handle_resume_sandbox_inner( +async fn handle_start_sandbox_inner( state: &Arc, - request: Request, + request: Request, ) -> Result, Status> { let principal = super::extract_principal(&request)?; let req = request.into_inner(); @@ -845,8 +845,8 @@ async fn handle_resume_sandbox_inner( let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) .await? .name; - let sandbox = state.compute.resume_sandbox(&workspace, &req.name).await?; - info!(sandbox_name = %req.name, "ResumeSandbox request completed successfully"); + let sandbox = state.compute.start_sandbox(&workspace, &req.name).await?; + info!(sandbox_name = %req.name, "StartSandbox request completed successfully"); Ok(Response::new(SandboxResponse { sandbox: Some(sandbox), })) @@ -4517,17 +4517,17 @@ mod tests { ); for result in [ - handle_suspend_sandbox( + handle_stop_sandbox( &state, - non_member_request(SuspendSandboxRequest { + non_member_request(StopSandboxRequest { workspace: "no-such-ws".into(), name: "any".into(), }), ) .await, - handle_resume_sandbox( + handle_start_sandbox( &state, - non_member_request(ResumeSandboxRequest { + non_member_request(StartSandboxRequest { workspace: "no-such-ws".into(), name: "any".into(), }), diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 5cd06d3900..6acc5f908f 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -484,9 +484,9 @@ pub(crate) async fn run_server( let (shutdown_tx, shutdown_rx) = watch::channel(false); - // Resume sandboxes that were stopped during the previous gateway + // Start sandboxes that were stopped during the previous gateway // shutdown so the running compute state matches the persisted store. - // Runs before watchers spawn so the watch loop sees the post-resume + // Runs before watchers spawn so the watch loop sees the post-start // snapshot on its first poll. ensure_default_workspace(&store).await?; @@ -496,8 +496,8 @@ pub(crate) async fn run_server( ) .await?; - if let Err(err) = state.compute.resume_persisted_sandboxes().await { - warn!(error = %err, "Failed to resume persisted sandboxes during startup"); + if let Err(err) = state.compute.start_persisted_sandboxes().await { + warn!(error = %err, "Failed to start persisted sandboxes during startup"); } state.compute.spawn_watchers(shutdown_rx.clone()); @@ -1570,10 +1570,10 @@ mod tests { } #[tokio::test] - async fn failed_gateway_listener_bind_does_not_attempt_persisted_sandbox_resume() { + async fn failed_gateway_listener_bind_does_not_attempt_persisted_sandbox_start() { let occupied_listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let occupied_address = occupied_listener.local_addr().unwrap(); - let resume_attempted = AtomicBool::new(false); + let start_attempted = AtomicBool::new(false); let primary_address: SocketAddr = "127.0.0.1:0".parse().unwrap(); let result: openshell_core::Result<()> = async { @@ -1582,7 +1582,7 @@ mod tests { &[docker_listener_requirement(occupied_address)], ) .await?; - resume_attempted.store(true, Ordering::SeqCst); + start_attempted.store(true, Ordering::SeqCst); Ok(()) } .await; @@ -1592,8 +1592,8 @@ mod tests { "binding the occupied extra gateway address should fail" ); assert!( - !resume_attempted.load(Ordering::SeqCst), - "persisted sandbox resume must not run before every gateway listener is bound" + !start_attempted.load(Ordering::SeqCst), + "persisted sandbox start must not run before every gateway listener is bound" ); } diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index 11a1eac540..11ef55978b 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -143,7 +143,7 @@ impl SupervisorSessionRegistry { /// Disconnect the current supervisor session for a sandbox. /// - /// Lifecycle suspension uses this to ensure a later resume must establish + /// Lifecycle stop uses this to ensure a later start must establish /// a fresh session before the sandbox can return to Ready. pub fn disconnect(&self, sandbox_id: &str) -> bool { let session = self.sessions.lock().unwrap().remove(sandbox_id); diff --git a/crates/openshell-server/src/test_support.rs b/crates/openshell-server/src/test_support.rs index 43c7444178..016e8d56af 100644 --- a/crates/openshell-server/src/test_support.rs +++ b/crates/openshell-server/src/test_support.rs @@ -11,7 +11,7 @@ use openshell_core::proto::compute::v1::{ DriverSandbox, GatewayListenerRequirement, GetCapabilitiesRequest, GetCapabilitiesResponse, GetGatewayListenerRequirementsRequest, GetGatewayListenerRequirementsResponse, GetSandboxRequest, GetSandboxResponse, ListSandboxesRequest, ListSandboxesResponse, - ResumeSandboxRequest, ResumeSandboxResponse, StopSandboxRequest, StopSandboxResponse, + StartSandboxRequest, StartSandboxResponse, StopSandboxRequest, StopSandboxResponse, ValidateSandboxCreateRequest, ValidateSandboxCreateResponse, WatchSandboxesEvent, WatchSandboxesRequest, compute_driver_server::ComputeDriver, gateway_listener_requirement::Selector, @@ -52,7 +52,7 @@ pub enum FakeComputeDriverCall { sandbox_id: String, sandbox_name: String, }, - ResumeSandbox { + StartSandbox { sandbox_id: String, sandbox_name: String, }, @@ -349,19 +349,19 @@ impl ComputeDriver for FakeComputeDriver { Ok(Response::new(StopSandboxResponse {})) } - async fn resume_sandbox( + async fn start_sandbox( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { self.record_traceparent(request.metadata()); let request = request.into_inner(); self.with_state(|state| { - state.calls.push(FakeComputeDriverCall::ResumeSandbox { + state.calls.push(FakeComputeDriverCall::StartSandbox { sandbox_id: request.sandbox_id, sandbox_name: request.sandbox_name, }); }); - Ok(Response::new(ResumeSandboxResponse {})) + Ok(Response::new(StartSandboxResponse {})) } async fn delete_sandbox( diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index 7bdb7b759c..a2df4755f0 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -83,16 +83,16 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _request: tonic::Request, + _request: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-server/tests/supervisor_relay_integration.rs b/crates/openshell-server/tests/supervisor_relay_integration.rs index 4809433f6b..86c7354647 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -130,15 +130,15 @@ impl OpenShell for RelayGateway { ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn suspend_sandbox( + async fn stop_sandbox( &self, - _: tonic::Request, + _: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } - async fn resume_sandbox( + async fn start_sandbox( &self, - _: tonic::Request, + _: tonic::Request, ) -> Result, Status> { Err(Status::unimplemented("unused")) } diff --git a/crates/openshell-tui/src/lib.rs b/crates/openshell-tui/src/lib.rs index c8caf78110..1f610015b4 100644 --- a/crates/openshell-tui/src/lib.rs +++ b/crates/openshell-tui/src/lib.rs @@ -2687,9 +2687,9 @@ fn phase_label(phase: i32) -> String { x if x == SandboxPhase::Ready as i32 => "Ready", x if x == SandboxPhase::Error as i32 => "Error", x if x == SandboxPhase::Deleting as i32 => "Deleting", - x if x == SandboxPhase::Suspending as i32 => "Suspending", - x if x == SandboxPhase::Suspended as i32 => "Suspended", - x if x == SandboxPhase::Resuming as i32 => "Resuming", + x if x == SandboxPhase::Stopping as i32 => "Stopping", + x if x == SandboxPhase::Stopped as i32 => "Stopped", + x if x == SandboxPhase::Starting as i32 => "Starting", _ => "Unknown", } .to_string() @@ -2724,10 +2724,10 @@ mod phase_label_tests { use super::*; #[test] - fn phase_label_covers_suspend_and_resume_lifecycle() { - assert_eq!(phase_label(SandboxPhase::Suspending as i32), "Suspending"); - assert_eq!(phase_label(SandboxPhase::Suspended as i32), "Suspended"); - assert_eq!(phase_label(SandboxPhase::Resuming as i32), "Resuming"); + fn phase_label_covers_stop_and_start_lifecycle() { + assert_eq!(phase_label(SandboxPhase::Stopping as i32), "Stopping"); + assert_eq!(phase_label(SandboxPhase::Stopped as i32), "Stopped"); + assert_eq!(phase_label(SandboxPhase::Starting as i32), "Starting"); } } diff --git a/crates/openshell-tui/src/ui/sandbox_detail.rs b/crates/openshell-tui/src/ui/sandbox_detail.rs index e2fc605f37..434f369d39 100644 --- a/crates/openshell-tui/src/ui/sandbox_detail.rs +++ b/crates/openshell-tui/src/ui/sandbox_detail.rs @@ -23,15 +23,15 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect) { let phase_style = match phase { "Ready" => t.status_ok, - "Provisioning" | "Suspending" | "Resuming" => t.status_warn, + "Provisioning" | "Stopping" | "Starting" => t.status_warn, "Error" => t.status_err, _ => t.muted, }; let status_indicator = match phase { "Ready" => "●", - "Provisioning" | "Suspending" | "Resuming" => "◐", - "Error" | "Suspended" => "○", + "Provisioning" | "Stopping" | "Starting" => "◐", + "Error" | "Stopped" => "○", _ => "…", }; diff --git a/crates/openshell-tui/src/ui/sandboxes.rs b/crates/openshell-tui/src/ui/sandboxes.rs index 41247b988b..d927537189 100644 --- a/crates/openshell-tui/src/ui/sandboxes.rs +++ b/crates/openshell-tui/src/ui/sandboxes.rs @@ -41,7 +41,7 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) { let phase_style = match phase { "Ready" => t.status_ok, - "Provisioning" | "Suspending" | "Resuming" => t.status_warn, + "Provisioning" | "Stopping" | "Starting" => t.status_warn, "Error" => t.status_err, _ => t.muted, }; diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index 1d51d4691d..b05bf05581 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -8,14 +8,14 @@ keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Docker, Podman, position: 4 --- -The gateway's configured compute driver determines how OpenShell creates each sandbox. The CLI workflow stays the same across drivers: you create, connect to, inspect, suspend, resume, and delete sandboxes through the gateway API. +The gateway's configured compute driver determines how OpenShell creates each sandbox. The CLI workflow stays the same across drivers: you create, connect to, inspect, stop, start, and delete sandboxes through the gateway API. Every compute driver runs the OpenShell supervisor inside the sandbox workload. The supervisor launches the agent process, applies policy, routes egress through the proxy, injects configured credentials, and maintains the gateway session. -Suspend stops compute but retains the sandbox record and the driver's -persistent workspace boundary. Resume reactivates the same driver resource. +Stop stops compute but retains the sandbox record and the driver's +persistent workspace boundary. Start reactivates the same driver resource. Delete remains independent and removes compute plus driver-owned persistent -state. While a sandbox is suspended, gateway access paths and exposed services +state. While a sandbox is stopped, gateway access paths and exposed services remain unavailable. ## Configure a Compute Driver @@ -135,9 +135,9 @@ For maintainer-level implementation details, refer to the [Docker driver README] Select Docker with `compute_drivers = ["docker"]` in `[openshell.gateway]`. Configure Docker driver values such as `socket_path`, `grpc_endpoint`, `network_name`, `supervisor_bin`, `supervisor_image`, `image_pull_policy`, `ssh_socket_path`, `sandbox_pids_limit`, and `guest_tls_*` in `[openshell.drivers.docker]`. When `socket_path` is unset, the driver uses the same responsive local socket selected by auto-detection. An explicitly selected Docker driver falls back to `/var/run/docker.sock` when no candidate responds. -Suspend stops the existing Docker container without removing its writable -layer or attached volumes. Resume starts that same container. A durably -suspended container stays stopped across gateway restart, and delete remains +Stop stops the existing Docker container without removing its writable +layer or attached volumes. Start starts that same container. A durably +stopped container stays stopped across gateway restart, and delete remains responsible for removing it. For GPU-backed Docker sandboxes, configure Docker CDI before starting the gateway so OpenShell can detect the daemon capability. @@ -208,8 +208,8 @@ Select Podman with `compute_drivers = ["podman"]` in `[openshell.gateway]`. Conf Podman sandboxes default to a 45-second graceful stop window before Podman escalates from `SIGTERM` to `SIGKILL`. Set `stop_timeout_secs` in gateway config, or `OPENSHELL_STOP_TIMEOUT` for the standalone driver, when a local runtime needs a different teardown window. -Suspend stops the existing Podman container while retaining its named workspace -volume and driver-owned secrets. Resume starts the same container. Delete is +Stop stops the existing Podman container while retaining its named workspace +volume and driver-owned secrets. Start starts the same container. Delete is the operation that removes the container and named volume. For proxy-required networks, the Podman driver also accepts the corporate egress proxy keys `https_proxy`, `no_proxy`, `proxy_auth_file`, `proxy_auth_allow_insecure`, and `proxy_connect_by_hostname`. The supervisor chains policy-approved TLS tunnels through the proxy with HTTP CONNECT instead of dialing destinations directly. See the [Gateway Configuration File](./gateway-config) reference for the full contract, including the cleartext-credential acknowledgement and the validated-IP CONNECT behavior. @@ -284,9 +284,9 @@ VM sandbox creation follows the same progress model as Kubernetes-backed sandbox On gateway restart, the gateway starts a fresh VM driver process. The driver scans its state directory for accepted sandbox launch records, restarts those VMs, and reuses each sandbox's existing `overlay.ext4` so files written inside the sandbox remain available after the supervisor reconnects. -Suspended VM state directories contain a marker that prevents startup from +Stopped VM state directories contain a marker that prevents startup from launching the VM. The driver retains `sandbox.pb`, `overlay.ext4`, and extension -state, then removes the marker and restores the same overlay on resume. +state, then removes the marker and restores the same overlay on start. For maintainer-level implementation details, refer to the [VM driver README](https://github.com/NVIDIA/OpenShell/blob/main/crates/openshell-driver-vm/README.md). @@ -377,7 +377,7 @@ process/binary identity through `/proc/`. The Kubernetes driver creates namespaced `agents.x-k8s.io` `Sandbox` resources from the Kubernetes SIG Apps [agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox) project. It detects the served Sandbox API at runtime, caches the selected API version for the gateway process, and uses `v1beta1` when available before falling back to `v1alpha1`, so supported Agent Sandbox installations work without version-specific operator configuration. The Agent Sandbox controller turns those resources into sandbox pods and related storage. -Suspend patches the existing resource rather than deleting it. For `v1beta1`, +Stop patches the existing resource rather than deleting it. For `v1beta1`, the driver sets `spec.operatingMode` to `Suspended` or `Running`. For `v1alpha1`, it sets `spec.replicas` to `0` or `1`. The Sandbox resource and its workspace PVC keep their identity across both operations. diff --git a/docs/sandboxes/manage-sandboxes.mdx b/docs/sandboxes/manage-sandboxes.mdx index 0218fb5cfb..9fb8d512f8 100644 --- a/docs/sandboxes/manage-sandboxes.mdx +++ b/docs/sandboxes/manage-sandboxes.mdx @@ -436,24 +436,24 @@ back to an unfiltered upload and prints a warning. Pass `--no-git-ignore` to opt into unfiltered uploads explicitly, upload a path outside the Git work tree, or force-add the intended files if they should remain Git-aware. -## Suspend and Resume Sandboxes +## Stop and Start Sandboxes -Suspend compute when you want to retain a sandbox and its persistent workspace +Stop compute when you want to retain a sandbox and its persistent workspace without keeping its container, pod, or VM running: ```shell -openshell sandbox suspend my-sandbox -openshell sandbox resume my-sandbox +openshell sandbox stop my-sandbox +openshell sandbox start my-sandbox ``` -The name is optional and defaults to the last-used sandbox. Suspend stops local -background forwards and waits for the `Suspended` phase. Resume waits until the -same sandbox returns to `Ready`. While suspended, you cannot connect, execute +The name is optional and defaults to the last-used sandbox. Stop stops local +background forwards and waits for the `Stopped` phase. Start waits until the +same sandbox returns to `Ready`. While stopped, you cannot connect, execute commands, transfer files, forward ports, or reach exposed services. Policies, provider attachments, settings, service definitions, and persistent workspace data remain associated with the sandbox. -Suspend and resume are idempotent. Delete a suspended sandbox normally when you +Stop and start are idempotent. Delete a stopped sandbox normally when you no longer need its retained state. ## Delete Sandboxes @@ -472,9 +472,9 @@ Every sandbox moves through a defined set of phases: | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Provisioning | The runtime is setting up the sandbox environment, or the gateway is waiting for the sandbox supervisor to establish its authenticated control session. | | Ready | The sandbox is running and its supervisor control session is connected. You can connect, execute commands, sync files, and view logs. | -| Suspending | The gateway accepted a suspend request and is stopping compute while retaining persistent state. | -| Suspended | Compute is stopped and access is unavailable. The sandbox record and driver-owned persistent workspace remain. | -| Resuming | Compute is restarting. The sandbox becomes usable only after a fresh supervisor session connects. | +| Stopping | The gateway accepted a stop request and is stopping compute while retaining persistent state. | +| Stopped | Compute is stopped and access is unavailable. The sandbox record and driver-owned persistent workspace remain. | +| Starting | Compute is starting. The sandbox becomes usable only after a fresh supervisor session connects. | | Error | Something went wrong during provisioning or execution. Check logs with `openshell logs` for details. | | Deleting | The sandbox is being torn down. The system releases resources and purges credentials. | diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index 3353f07af7..76881d1f5e 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -55,8 +55,8 @@ path = "tests/driver_config_volume.rs" required-features = ["e2e-local-container-driver"] [[test]] -name = "gateway_resume" -path = "tests/gateway_resume.rs" +name = "gateway_start" +path = "tests/gateway_start.rs" required-features = ["e2e-docker"] [[test]] @@ -65,8 +65,8 @@ path = "tests/local_driver_token_restart.rs" required-features = ["e2e"] [[test]] -name = "podman_gateway_resume" -path = "tests/podman_gateway_resume.rs" +name = "podman_gateway_start" +path = "tests/podman_gateway_start.rs" required-features = ["e2e-podman"] [[test]] @@ -80,8 +80,8 @@ path = "tests/podman_oci_identity.rs" required-features = ["e2e-podman"] [[test]] -name = "vm_gateway_resume" -path = "tests/vm_gateway_resume.rs" +name = "vm_gateway_start" +path = "tests/vm_gateway_start.rs" required-features = ["e2e-vm"] [[test]] diff --git a/e2e/rust/e2e-vm.sh b/e2e/rust/e2e-vm.sh index 43b573f867..26671ccb86 100755 --- a/e2e/rust/e2e-vm.sh +++ b/e2e/rust/e2e-vm.sh @@ -366,5 +366,5 @@ if [ -n "${E2E_TEST_OVERRIDE}" ]; then else run_e2e_test smoke run_e2e_test host_gateway_alias - run_e2e_test vm_gateway_resume + run_e2e_test vm_gateway_start fi diff --git a/e2e/rust/tests/gateway_resume.rs b/e2e/rust/tests/gateway_start.rs similarity index 90% rename from e2e/rust/tests/gateway_resume.rs rename to e2e/rust/tests/gateway_start.rs index 8f850e485d..3984cf1907 100644 --- a/e2e/rust/tests/gateway_resume.rs +++ b/e2e/rust/tests/gateway_start.rs @@ -3,7 +3,7 @@ #![cfg(feature = "e2e")] -//! E2E coverage for resuming Docker sandboxes after a standalone gateway restart. +//! E2E coverage for starting Docker sandboxes after a standalone gateway restart. //! //! This intentionally targets the Docker-driver gateway started by //! `e2e/with-docker-gateway.sh`. Existing-endpoint E2E runs do not own the @@ -20,8 +20,8 @@ use openshell_e2e::harness::sandbox::SandboxGuard; use tokio::time::sleep; const MANAGED_BY_LABEL_FILTER: &str = "label=openshell.ai/managed-by=openshell"; -const READY_MARKER: &str = "gateway-resume-ready"; -const RESUME_FILE: &str = "/sandbox/gateway-resume-state"; +const READY_MARKER: &str = "gateway-start-ready"; +const START_FILE: &str = "/sandbox/gateway-start-state"; const SANDBOX_NAMESPACE_LABEL: &str = "openshell.ai/sandbox-namespace"; const SANDBOX_NAME_LABEL: &str = "openshell.ai/sandbox-name"; @@ -117,17 +117,17 @@ async fn wait_for_container_running( } #[tokio::test] -async fn docker_gateway_restart_resumes_running_sandbox() { +async fn docker_gateway_restart_starts_running_sandbox() { let Some(gateway) = ManagedGateway::from_env().expect("load managed e2e gateway metadata") else { - eprintln!("Skipping gateway resume test: e2e gateway is not managed by this test run"); + eprintln!("Skipping gateway start test: e2e gateway is not managed by this test run"); return; }; let Some(namespace) = std::env::var("OPENSHELL_E2E_DOCKER_NETWORK_NAME") .ok() .filter(|value| !value.trim().is_empty()) else { - eprintln!("Skipping gateway resume test: Docker e2e namespace is unavailable"); + eprintln!("Skipping gateway start test: Docker e2e namespace is unavailable"); return; }; @@ -136,14 +136,14 @@ async fn docker_gateway_restart_resumes_running_sandbox() { .expect("gateway should start healthy"); let script = format!( - "echo before-restart > {RESUME_FILE}; echo {READY_MARKER}; while true; do sleep 1; done" + "echo before-restart > {START_FILE}; echo {READY_MARKER}; while true; do sleep 1; done" ); let mut sandbox = SandboxGuard::create_keep(&["sh", "-lc", &script], READY_MARKER) .await .expect("create long-running sandbox"); let before_restart = sandbox - .exec(&["cat", RESUME_FILE]) + .exec(&["cat", START_FILE]) .await .expect("read sandbox state before restart"); assert!( @@ -166,7 +166,7 @@ async fn docker_gateway_restart_resumes_running_sandbox() { .expect("gateway should become healthy after restart"); wait_for_container_running(&namespace, &sandbox.name, true, Duration::from_secs(120)) .await - .expect("gateway startup should resume the Docker sandbox container"); + .expect("gateway startup should start the Docker sandbox container"); let names = sandbox_names().await.expect("list sandboxes after restart"); assert!( @@ -177,7 +177,7 @@ async fn docker_gateway_restart_resumes_running_sandbox() { wait_for_sandbox_exec_contains( &sandbox.name, - &["cat", RESUME_FILE], + &["cat", START_FILE], "before-restart", Duration::from_secs(240), ) diff --git a/e2e/rust/tests/podman_gateway_resume.rs b/e2e/rust/tests/podman_gateway_start.rs similarity index 77% rename from e2e/rust/tests/podman_gateway_resume.rs rename to e2e/rust/tests/podman_gateway_start.rs index 2600202537..e72769cb80 100644 --- a/e2e/rust/tests/podman_gateway_resume.rs +++ b/e2e/rust/tests/podman_gateway_start.rs @@ -3,12 +3,12 @@ #![cfg(feature = "e2e-podman")] -//! Podman-specific E2E coverage for resuming sandboxes after a standalone +//! Podman-specific E2E coverage for starting sandboxes after a standalone //! gateway restart. //! //! Unlike the Docker driver, Podman does not stop sandbox containers when the //! gateway process exits — the containers keep running and the restarted -//! gateway re-adopts them. This test follows the `vm_gateway_resume.rs` +//! gateway re-adopts them. This test follows the `vm_gateway_start.rs` //! pattern: verify sandbox survival at the application level without asserting //! intermediate container-state transitions. @@ -20,19 +20,19 @@ use openshell_e2e::harness::cli::{ use openshell_e2e::harness::gateway::ManagedGateway; use openshell_e2e::harness::sandbox::SandboxGuard; -const READY_MARKER: &str = "podman-gateway-resume-ready"; -const RESUME_FILE: &str = "/sandbox/podman-gateway-resume-state"; +const READY_MARKER: &str = "podman-gateway-start-ready"; +const START_FILE: &str = "/sandbox/podman-gateway-start-state"; #[tokio::test] -async fn podman_gateway_restart_resumes_running_sandbox() { +async fn podman_gateway_restart_starts_running_sandbox() { if std::env::var("OPENSHELL_E2E_DRIVER").as_deref() != Ok("podman") { - eprintln!("Skipping Podman gateway resume test: e2e driver is not podman"); + eprintln!("Skipping Podman gateway start test: e2e driver is not podman"); return; } let Some(gateway) = ManagedGateway::from_env().expect("load managed e2e gateway metadata") else { eprintln!( - "Skipping Podman gateway resume test: e2e gateway is not managed by this test run" + "Skipping Podman gateway start test: e2e gateway is not managed by this test run" ); return; }; @@ -42,14 +42,14 @@ async fn podman_gateway_restart_resumes_running_sandbox() { .expect("gateway should start healthy"); let script = format!( - "echo before-restart > {RESUME_FILE}; echo {READY_MARKER}; while true; do sleep 1; done" + "echo before-restart > {START_FILE}; echo {READY_MARKER}; while true; do sleep 1; done" ); let mut sandbox = SandboxGuard::create_keep(&["sh", "-lc", &script], READY_MARKER) .await .expect("create long-running Podman sandbox"); let before_restart = sandbox - .exec(&["cat", RESUME_FILE]) + .exec(&["cat", START_FILE]) .await .expect("read Podman sandbox state before restart"); assert!( @@ -72,7 +72,7 @@ async fn podman_gateway_restart_resumes_running_sandbox() { wait_for_sandbox_exec_contains( &sandbox.name, - &["cat", RESUME_FILE], + &["cat", START_FILE], "before-restart", Duration::from_secs(240), ) diff --git a/e2e/rust/tests/sandbox_lifecycle.rs b/e2e/rust/tests/sandbox_lifecycle.rs index a7de33d656..75e34b3b4f 100644 --- a/e2e/rust/tests/sandbox_lifecycle.rs +++ b/e2e/rust/tests/sandbox_lifecycle.rs @@ -130,19 +130,19 @@ async fn run_sandbox_lifecycle_command(operation: &str, name: &str) -> String { } #[tokio::test] -async fn sandbox_suspend_resume_preserves_workspace() { - const SENTINEL: &str = "openshell-suspend-resume-sentinel"; - const SENTINEL_PATH: &str = "/sandbox/.openshell-suspend-resume-e2e"; +async fn sandbox_stop_start_preserves_workspace() { + const SENTINEL: &str = "openshell-stop-start-sentinel"; + const SENTINEL_PATH: &str = "/sandbox/.openshell-stop-start-e2e"; let write_sentinel = format!("printf '%s\\n' '{SENTINEL}' > '{SENTINEL_PATH}'"); let mut sandbox = SandboxGuard::create(&["--", "sh", "-lc", &write_sentinel]) .await .expect("sandbox create should write the workspace sentinel"); - let suspend_output = run_sandbox_lifecycle_command("suspend", &sandbox.name).await; + let stop_output = run_sandbox_lifecycle_command("stop", &sandbox.name).await; assert!( - suspend_output.contains("Suspended sandbox"), - "expected suspend confirmation in:\n{suspend_output}", + stop_output.contains("Stopped sandbox"), + "expected stop confirmation in:\n{stop_output}", ); let mut exec_cmd = openshell_cmd(); @@ -159,43 +159,43 @@ async fn sandbox_suspend_resume_preserves_workspace() { ]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let suspended_exec = exec_cmd + let stopped_exec = exec_cmd .output() .await - .expect("spawn openshell sandbox exec while suspended"); + .expect("spawn openshell sandbox exec while stopped"); assert!( - !suspended_exec.status.success(), - "sandbox exec should fail while suspended" + !stopped_exec.status.success(), + "sandbox exec should fail while stopped" ); - let resume_output = run_sandbox_lifecycle_command("resume", &sandbox.name).await; + let start_output = run_sandbox_lifecycle_command("start", &sandbox.name).await; assert!( - resume_output.contains("Resumed sandbox"), - "expected resume confirmation in:\n{resume_output}", + start_output.contains("Started sandbox"), + "expected start confirmation in:\n{start_output}", ); let sentinel = sandbox .exec(&["cat", SENTINEL_PATH]) .await - .expect("sandbox exec should succeed after resume"); + .expect("sandbox exec should succeed after start"); assert!( sentinel.lines().any(|line| line.trim() == SENTINEL), - "workspace sentinel should survive suspend and resume:\n{sentinel}", + "workspace sentinel should survive stop and start:\n{sentinel}", ); sandbox.cleanup().await; } #[tokio::test] -async fn sandbox_can_be_deleted_while_suspended() { +async fn sandbox_can_be_deleted_while_stopped() { let mut sandbox = SandboxGuard::create(&["--", "true"]) .await .expect("sandbox create should succeed"); - let suspend_output = run_sandbox_lifecycle_command("suspend", &sandbox.name).await; + let stop_output = run_sandbox_lifecycle_command("stop", &sandbox.name).await; assert!( - suspend_output.contains("Suspended sandbox"), - "expected suspend confirmation in:\n{suspend_output}", + stop_output.contains("Stopped sandbox"), + "expected stop confirmation in:\n{stop_output}", ); let delete_output = run_sandbox_lifecycle_command("delete", &sandbox.name).await; @@ -207,7 +207,7 @@ async fn sandbox_can_be_deleted_while_suspended() { if let Err(last_sandbox_list) = assert_sandbox_presence_eventually(&sandbox.name, false).await { sandbox.cleanup().await; panic!( - "suspended sandbox {} should be deleted without resuming after \ + "stopped sandbox {} should be deleted without starting after \ {SANDBOX_PRESENCE_TIMEOUT:?}; last observed sandbox list: {last_sandbox_list:?}", sandbox.name, ); diff --git a/e2e/rust/tests/vm_gateway_resume.rs b/e2e/rust/tests/vm_gateway_start.rs similarity index 79% rename from e2e/rust/tests/vm_gateway_resume.rs rename to e2e/rust/tests/vm_gateway_start.rs index 4c502bb9c9..923198668b 100644 --- a/e2e/rust/tests/vm_gateway_resume.rs +++ b/e2e/rust/tests/vm_gateway_start.rs @@ -3,7 +3,7 @@ #![cfg(feature = "e2e-vm")] -//! VM-specific E2E coverage for resuming sandboxes after a standalone gateway +//! VM-specific E2E coverage for starting sandboxes after a standalone gateway //! restart. //! //! This test is gated behind the `e2e-vm` feature because it requires the VM @@ -17,18 +17,18 @@ use openshell_e2e::harness::cli::{ use openshell_e2e::harness::gateway::ManagedGateway; use openshell_e2e::harness::sandbox::SandboxGuard; -const READY_MARKER: &str = "vm-gateway-resume-ready"; -const RESUME_FILE: &str = "/sandbox/vm-gateway-resume-state"; +const READY_MARKER: &str = "vm-gateway-start-ready"; +const START_FILE: &str = "/sandbox/vm-gateway-start-state"; #[tokio::test] -async fn vm_gateway_restart_resumes_running_sandbox() { +async fn vm_gateway_restart_starts_running_sandbox() { if std::env::var("OPENSHELL_E2E_DRIVER").as_deref() != Ok("vm") { - eprintln!("Skipping VM gateway resume test: e2e driver is not vm"); + eprintln!("Skipping VM gateway start test: e2e driver is not vm"); return; } let Some(gateway) = ManagedGateway::from_env().expect("load managed e2e gateway metadata") else { - eprintln!("Skipping VM gateway resume test: e2e gateway is not managed by this test run"); + eprintln!("Skipping VM gateway start test: e2e gateway is not managed by this test run"); return; }; @@ -40,14 +40,14 @@ async fn vm_gateway_restart_resumes_running_sandbox() { // overlay. Flush the marker before reporting readiness so the assertion // verifies durable overlay state rather than guest page-cache timing. let script = format!( - "echo before-restart > {RESUME_FILE}; sync; echo {READY_MARKER}; while true; do sleep 1; done" + "echo before-restart > {START_FILE}; sync; echo {READY_MARKER}; while true; do sleep 1; done" ); let mut sandbox = SandboxGuard::create_keep(&["sh", "-lc", &script], READY_MARKER) .await .expect("create long-running VM sandbox"); let before_restart = sandbox - .exec(&["cat", RESUME_FILE]) + .exec(&["cat", START_FILE]) .await .expect("read VM sandbox state before restart"); assert!( @@ -70,7 +70,7 @@ async fn vm_gateway_restart_resumes_running_sandbox() { wait_for_sandbox_exec_contains( &sandbox.name, - &["cat", RESUME_FILE], + &["cat", START_FILE], "before-restart", Duration::from_secs(240), ) diff --git a/proto/compute_driver.proto b/proto/compute_driver.proto index e916c1d4df..3a0b7609ab 100644 --- a/proto/compute_driver.proto +++ b/proto/compute_driver.proto @@ -43,8 +43,8 @@ service ComputeDriver { // Idempotently stop platform resources without deleting persistent state. rpc StopSandbox(StopSandboxRequest) returns (StopSandboxResponse); - // Idempotently resume platform resources for a stopped sandbox. - rpc ResumeSandbox(ResumeSandboxRequest) returns (ResumeSandboxResponse); + // Idempotently start platform resources for a stopped sandbox. + rpc StartSandbox(StartSandboxRequest) returns (StartSandboxResponse); // Tear down platform resources for a sandbox. rpc DeleteSandbox(DeleteSandboxRequest) returns (DeleteSandboxResponse); @@ -289,14 +289,14 @@ message StopSandboxRequest { message StopSandboxResponse {} -message ResumeSandboxRequest { +message StartSandboxRequest { // Stable sandbox ID stored by the gateway. string sandbox_id = 1; // Compute-runtime name used by the driver. string sandbox_name = 2; } -message ResumeSandboxResponse {} +message StartSandboxResponse {} message DeleteSandboxRequest { // Stable sandbox ID stored by the gateway. diff --git a/proto/openshell.proto b/proto/openshell.proto index b923ec30db..5f3b660ae4 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -108,8 +108,8 @@ service OpenShell { }; } - // Suspend a sandbox while retaining its persistent state. - rpc SuspendSandbox(SuspendSandboxRequest) returns (SandboxResponse) { + // Stop a sandbox while retaining its persistent state. + rpc StopSandbox(StopSandboxRequest) returns (SandboxResponse) { option (openshell.options.v1.authorization) = { auth_mode: "bearer" scope: "sandbox:write" @@ -117,8 +117,8 @@ service OpenShell { }; } - // Resume a previously suspended sandbox. - rpc ResumeSandbox(ResumeSandboxRequest) returns (SandboxResponse) { + // Start a previously stopped sandbox. + rpc StartSandbox(StartSandboxRequest) returns (SandboxResponse) { option (openshell.options.v1.authorization) = { auth_mode: "bearer" scope: "sandbox:write" @@ -897,9 +897,9 @@ enum SandboxPhase { SANDBOX_PHASE_ERROR = 3; SANDBOX_PHASE_DELETING = 4; SANDBOX_PHASE_UNKNOWN = 5; - SANDBOX_PHASE_SUSPENDING = 6; - SANDBOX_PHASE_SUSPENDED = 7; - SANDBOX_PHASE_RESUMING = 8; + SANDBOX_PHASE_STOPPING = 6; + SANDBOX_PHASE_STOPPED = 7; + SANDBOX_PHASE_STARTING = 8; } // Public platform event exposed on the sandbox watch stream. @@ -997,16 +997,16 @@ message DeleteSandboxRequest { string workspace = 2; } -// Suspend sandbox request. -message SuspendSandboxRequest { +// Stop sandbox request. +message StopSandboxRequest { // Sandbox name (canonical lookup key). string name = 1; // Workspace scope. Empty defaults to "default". string workspace = 2; } -// Resume sandbox request. -message ResumeSandboxRequest { +// Start sandbox request. +message StartSandboxRequest { // Sandbox name (canonical lookup key). string name = 1; // Workspace scope. Empty defaults to "default". diff --git a/python/openshell/sandbox.py b/python/openshell/sandbox.py index d976014b99..c96c647050 100644 --- a/python/openshell/sandbox.py +++ b/python/openshell/sandbox.py @@ -250,14 +250,12 @@ def exec_python( def delete(self) -> bool: return self._client.delete(self.sandbox.name, workspace=self._workspace) - def suspend(self) -> SandboxRef: - self.sandbox = self._client.suspend( - self.sandbox.name, workspace=self._workspace - ) + def stop(self) -> SandboxRef: + self.sandbox = self._client.stop(self.sandbox.name, workspace=self._workspace) return self.sandbox - def resume(self) -> SandboxRef: - self.sandbox = self._client.resume(self.sandbox.name, workspace=self._workspace) + def start(self) -> SandboxRef: + self.sandbox = self._client.start(self.sandbox.name, workspace=self._workspace) return self.sandbox @@ -556,16 +554,16 @@ def delete(self, sandbox_name: str, *, workspace: str) -> bool: ) return bool(response.deleted) - def suspend(self, sandbox_name: str, *, workspace: str) -> SandboxRef: - response = self._stub.SuspendSandbox( - openshell_pb2.SuspendSandboxRequest(name=sandbox_name, workspace=workspace), + def stop(self, sandbox_name: str, *, workspace: str) -> SandboxRef: + response = self._stub.StopSandbox( + openshell_pb2.StopSandboxRequest(name=sandbox_name, workspace=workspace), timeout=self._timeout, ) return _sandbox_ref(response.sandbox) - def resume(self, sandbox_name: str, *, workspace: str) -> SandboxRef: - response = self._stub.ResumeSandbox( - openshell_pb2.ResumeSandboxRequest(name=sandbox_name, workspace=workspace), + def start(self, sandbox_name: str, *, workspace: str) -> SandboxRef: + response = self._stub.StartSandbox( + openshell_pb2.StartSandboxRequest(name=sandbox_name, workspace=workspace), timeout=self._timeout, ) return _sandbox_ref(response.sandbox) @@ -598,14 +596,14 @@ def wait_ready( timeout_seconds=timeout_seconds, ) - def wait_suspended( + def wait_stopped( self, sandbox_name: str, *, workspace: str, timeout_seconds: float = 300.0 ) -> SandboxRef: return self._wait_for_phase( sandbox_name, workspace=workspace, - target_phase=openshell_pb2.SANDBOX_PHASE_SUSPENDED, - target_name="suspended", + target_phase=openshell_pb2.SANDBOX_PHASE_STOPPED, + target_name="stopped", timeout_seconds=timeout_seconds, ) diff --git a/python/openshell/sandbox_test.py b/python/openshell/sandbox_test.py index 51a6bcb1c5..b6052d9903 100644 --- a/python/openshell/sandbox_test.py +++ b/python/openshell/sandbox_test.py @@ -1482,8 +1482,8 @@ def __init__(self, listed: list[openshell_pb2.Sandbox] | None = None) -> None: self.list_request: openshell_pb2.ListSandboxesRequest | None = None self.get_request: openshell_pb2.GetSandboxRequest | None = None self.delete_request: openshell_pb2.DeleteSandboxRequest | None = None - self.suspend_request: openshell_pb2.SuspendSandboxRequest | None = None - self.resume_request: openshell_pb2.ResumeSandboxRequest | None = None + self.stop_request: openshell_pb2.StopSandboxRequest | None = None + self.start_request: openshell_pb2.StartSandboxRequest | None = None self._listed = listed or [] def GetSandbox( @@ -1508,34 +1508,34 @@ def DeleteSandbox( _ = timeout return SimpleNamespace(deleted=True) - def SuspendSandbox( + def StopSandbox( self, - request: openshell_pb2.SuspendSandboxRequest, + request: openshell_pb2.StopSandboxRequest, timeout: float | None = None, ) -> Any: - self.suspend_request = request + self.stop_request = request _ = timeout return SimpleNamespace( sandbox=_make_sandbox_proto( "sandbox-1", request.name, - phase=openshell_pb2.SANDBOX_PHASE_SUSPENDED, + phase=openshell_pb2.SANDBOX_PHASE_STOPPED, workspace=request.workspace, ) ) - def ResumeSandbox( + def StartSandbox( self, - request: openshell_pb2.ResumeSandboxRequest, + request: openshell_pb2.StartSandboxRequest, timeout: float | None = None, ) -> Any: - self.resume_request = request + self.start_request = request _ = timeout return SimpleNamespace( sandbox=_make_sandbox_proto( "sandbox-1", request.name, - phase=openshell_pb2.SANDBOX_PHASE_RESUMING, + phase=openshell_pb2.SANDBOX_PHASE_STARTING, workspace=request.workspace, ) ) @@ -1614,21 +1614,21 @@ def test_create_forwards_name_and_labels() -> None: assert dict(ref.labels) == {"aiq": "deep-research"} -def test_suspend_and_resume_forward_workspace_and_return_phase() -> None: +def test_stop_and_start_forward_workspace_and_return_phase() -> None: stub = _FakeSandboxStub() client = _client_with_fake_stub(stub) - suspended = client.suspend("job-1", workspace="team-a") - assert stub.suspend_request is not None - assert stub.suspend_request.name == "job-1" - assert stub.suspend_request.workspace == "team-a" - assert suspended.phase == openshell_pb2.SANDBOX_PHASE_SUSPENDED - - resuming = client.resume("job-1", workspace="team-a") - assert stub.resume_request is not None - assert stub.resume_request.name == "job-1" - assert stub.resume_request.workspace == "team-a" - assert resuming.phase == openshell_pb2.SANDBOX_PHASE_RESUMING + stopped = client.stop("job-1", workspace="team-a") + assert stub.stop_request is not None + assert stub.stop_request.name == "job-1" + assert stub.stop_request.workspace == "team-a" + assert stopped.phase == openshell_pb2.SANDBOX_PHASE_STOPPED + + starting = client.start("job-1", workspace="team-a") + assert stub.start_request is not None + assert stub.start_request.name == "job-1" + assert stub.start_request.workspace == "team-a" + assert starting.phase == openshell_pb2.SANDBOX_PHASE_STARTING def test_create_without_args_sends_empty_metadata() -> None: diff --git a/rfc/0011-multi-player-design/README.md b/rfc/0011-multi-player-design/README.md index f28e140364..32a2f584e1 100644 --- a/rfc/0011-multi-player-design/README.md +++ b/rfc/0011-multi-player-design/README.md @@ -818,9 +818,9 @@ workspace authorization path. The gateway is the actor. workspace — the driver has no workspace concept. The gateway must query all stored sandboxes across all workspaces to produce the full set for comparison. -- **Startup resume** (`resume_persisted_sandboxes`). On gateway startup, the - resume path iterates all stored sandboxes whose phase indicates they should - be running and asks the driver to resume each one. This must cover all +- **Startup start** (`start_persisted_sandboxes`). On gateway startup, the + start path iterates all stored sandboxes whose phase indicates they should + be running and asks the driver to start each one. This must cover all workspaces. - **Provider credential refresh** (`refresh_provider_credential`). A background @@ -1144,7 +1144,7 @@ foundations. The work can be phased to deliver value incrementally: Backward compatibility is desirable but not a hard requirement at this stage — existing users must recreate service endpoints when upgrading. Add a cross-workspace `list_by_type(object_type, limit, offset)` store method - for internal infrastructure operations (reconciler, resume, provider refresh) + for internal infrastructure operations (reconciler, start, provider refresh) that need to query workspace-scoped resources across all workspaces. Thread workspace through `StoredProviderCredentialRefreshState` so the provider refresh worker can unambiguously resolve workspace-scoped providers — with @@ -1271,7 +1271,7 @@ depend only on Phase 1. has no access-control gate — it is a persistence-layer primitive. Authorization for cross-workspace queries is enforced at the gRPC handler level (Platform Admin check for `all_workspaces` on list RPCs) and by code-level access - control for internal operations (only the reconciler, resume, and refresh + control for internal operations (only the reconciler, start, and refresh worker call it). This relies on internal code discipline rather than an enforced store-level boundary. A future extension could add a store-level caller identity parameter if defense-in-depth is desired. diff --git a/sdk/go/openshell/v1/internal/converter/sandbox.go b/sdk/go/openshell/v1/internal/converter/sandbox.go index 8838480ed9..7dbd39968e 100644 --- a/sdk/go/openshell/v1/internal/converter/sandbox.go +++ b/sdk/go/openshell/v1/internal/converter/sandbox.go @@ -110,12 +110,12 @@ func SandboxPhaseFromProto(phase pb.SandboxPhase) types.SandboxPhase { return types.SandboxDeleting case pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN: return types.SandboxUnknown - case pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING: - return types.SandboxSuspending - case pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED: - return types.SandboxSuspended - case pb.SandboxPhase_SANDBOX_PHASE_RESUMING: - return types.SandboxResuming + case pb.SandboxPhase_SANDBOX_PHASE_STOPPING: + return types.SandboxStopping + case pb.SandboxPhase_SANDBOX_PHASE_STOPPED: + return types.SandboxStopped + case pb.SandboxPhase_SANDBOX_PHASE_STARTING: + return types.SandboxStarting default: return types.SandboxUnknown } @@ -134,12 +134,12 @@ func SandboxPhaseToProto(phase types.SandboxPhase) pb.SandboxPhase { return pb.SandboxPhase_SANDBOX_PHASE_DELETING case types.SandboxUnknown: return pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN - case types.SandboxSuspending: - return pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING - case types.SandboxSuspended: - return pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED - case types.SandboxResuming: - return pb.SandboxPhase_SANDBOX_PHASE_RESUMING + case types.SandboxStopping: + return pb.SandboxPhase_SANDBOX_PHASE_STOPPING + case types.SandboxStopped: + return pb.SandboxPhase_SANDBOX_PHASE_STOPPED + case types.SandboxStarting: + return pb.SandboxPhase_SANDBOX_PHASE_STARTING default: return pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN } diff --git a/sdk/go/openshell/v1/internal/converter/sandbox_test.go b/sdk/go/openshell/v1/internal/converter/sandbox_test.go index ec8c0d833a..258f433408 100644 --- a/sdk/go/openshell/v1/internal/converter/sandbox_test.go +++ b/sdk/go/openshell/v1/internal/converter/sandbox_test.go @@ -142,9 +142,9 @@ func TestSandboxPhaseFromProto(t *testing.T) { {pb.SandboxPhase_SANDBOX_PHASE_ERROR, v1.SandboxError}, {pb.SandboxPhase_SANDBOX_PHASE_DELETING, v1.SandboxDeleting}, {pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN, v1.SandboxUnknown}, - {pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING, v1.SandboxSuspending}, - {pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED, v1.SandboxSuspended}, - {pb.SandboxPhase_SANDBOX_PHASE_RESUMING, v1.SandboxResuming}, + {pb.SandboxPhase_SANDBOX_PHASE_STOPPING, v1.SandboxStopping}, + {pb.SandboxPhase_SANDBOX_PHASE_STOPPED, v1.SandboxStopped}, + {pb.SandboxPhase_SANDBOX_PHASE_STARTING, v1.SandboxStarting}, {pb.SandboxPhase_SANDBOX_PHASE_UNSPECIFIED, v1.SandboxUnknown}, {pb.SandboxPhase(999), v1.SandboxUnknown}, } @@ -164,9 +164,9 @@ func TestSandboxPhaseToProto(t *testing.T) { {v1.SandboxError, pb.SandboxPhase_SANDBOX_PHASE_ERROR}, {v1.SandboxDeleting, pb.SandboxPhase_SANDBOX_PHASE_DELETING}, {v1.SandboxUnknown, pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN}, - {v1.SandboxSuspending, pb.SandboxPhase_SANDBOX_PHASE_SUSPENDING}, - {v1.SandboxSuspended, pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED}, - {v1.SandboxResuming, pb.SandboxPhase_SANDBOX_PHASE_RESUMING}, + {v1.SandboxStopping, pb.SandboxPhase_SANDBOX_PHASE_STOPPING}, + {v1.SandboxStopped, pb.SandboxPhase_SANDBOX_PHASE_STOPPED}, + {v1.SandboxStarting, pb.SandboxPhase_SANDBOX_PHASE_STARTING}, {v1.SandboxPhase("bogus"), pb.SandboxPhase_SANDBOX_PHASE_UNKNOWN}, } diff --git a/sdk/go/openshell/v1/sandbox.go b/sdk/go/openshell/v1/sandbox.go index 9de8959907..6123ecd473 100644 --- a/sdk/go/openshell/v1/sandbox.go +++ b/sdk/go/openshell/v1/sandbox.go @@ -56,14 +56,14 @@ type SandboxInterface interface { Create(ctx context.Context, workspace, name string, spec *SandboxSpec, labels map[string]string) (*Sandbox, error) Get(ctx context.Context, workspace, name string) (*Sandbox, error) List(ctx context.Context, workspace string, opts ...ListOptions) ([]*Sandbox, error) - Suspend(ctx context.Context, workspace, name string) (*Sandbox, error) - Resume(ctx context.Context, workspace, name string) (*Sandbox, error) + Stop(ctx context.Context, workspace, name string) (*Sandbox, error) + Start(ctx context.Context, workspace, name string) (*Sandbox, error) Delete(ctx context.Context, workspace, name string) error AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) WaitReady(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) - WaitSuspended(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) + WaitStopped(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) Watch(ctx context.Context, workspace, name string, opts ...WatchOptions) (WatchInterface[*Sandbox], error) // GetLogs retrieves log entries for a sandbox. The sandbox is resolved // by name (an internal Get call translates name to ID). Use diff --git a/sdk/go/openshell/v1/sandbox_client.go b/sdk/go/openshell/v1/sandbox_client.go index 103bb764ef..8cf5d89a5d 100644 --- a/sdk/go/openshell/v1/sandbox_client.go +++ b/sdk/go/openshell/v1/sandbox_client.go @@ -91,8 +91,8 @@ func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) erro return nil } -func (s *sandboxClient) Suspend(ctx context.Context, workspace, name string) (*Sandbox, error) { - resp, err := s.client.SuspendSandbox(ctx, &pb.SuspendSandboxRequest{ +func (s *sandboxClient) Stop(ctx context.Context, workspace, name string) (*Sandbox, error) { + resp, err := s.client.StopSandbox(ctx, &pb.StopSandboxRequest{ Name: name, Workspace: workspace, }) @@ -102,8 +102,8 @@ func (s *sandboxClient) Suspend(ctx context.Context, workspace, name string) (*S return converter.SandboxFromProto(resp.GetSandbox()), nil } -func (s *sandboxClient) Resume(ctx context.Context, workspace, name string) (*Sandbox, error) { - resp, err := s.client.ResumeSandbox(ctx, &pb.ResumeSandboxRequest{ +func (s *sandboxClient) Start(ctx context.Context, workspace, name string) (*Sandbox, error) { + resp, err := s.client.StartSandbox(ctx, &pb.StartSandboxRequest{ Name: name, Workspace: workspace, }) @@ -165,8 +165,8 @@ func (s *sandboxClient) WaitReady(ctx context.Context, workspace, name string, o return s.waitForPhase(ctx, workspace, name, SandboxReady, opts...) } -func (s *sandboxClient) WaitSuspended(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) { - return s.waitForPhase(ctx, workspace, name, SandboxSuspended, opts...) +func (s *sandboxClient) WaitStopped(ctx context.Context, workspace, name string, opts ...WaitOptions) (*Sandbox, error) { + return s.waitForPhase(ctx, workspace, name, SandboxStopped, opts...) } func (s *sandboxClient) waitForPhase(ctx context.Context, workspace, name string, target SandboxPhase, opts ...WaitOptions) (*Sandbox, error) { diff --git a/sdk/go/openshell/v1/sandbox_client_test.go b/sdk/go/openshell/v1/sandbox_client_test.go index e2a8afa89d..bf06e852d1 100644 --- a/sdk/go/openshell/v1/sandbox_client_test.go +++ b/sdk/go/openshell/v1/sandbox_client_test.go @@ -125,25 +125,25 @@ func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandb return &pb.DeleteSandboxResponse{Deleted: true}, nil } -func (s *mockSandboxServer) SuspendSandbox(_ context.Context, req *pb.SuspendSandboxRequest) (*pb.SandboxResponse, error) { +func (s *mockSandboxServer) StopSandbox(_ context.Context, req *pb.StopSandboxRequest) (*pb.SandboxResponse, error) { s.mu.Lock() defer s.mu.Unlock() sb, ok := s.sandboxes[req.GetName()] if !ok { return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) } - sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED + sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_STOPPED return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil } -func (s *mockSandboxServer) ResumeSandbox(_ context.Context, req *pb.ResumeSandboxRequest) (*pb.SandboxResponse, error) { +func (s *mockSandboxServer) StartSandbox(_ context.Context, req *pb.StartSandboxRequest) (*pb.SandboxResponse, error) { s.mu.Lock() defer s.mu.Unlock() sb, ok := s.sandboxes[req.GetName()] if !ok { return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName()) } - sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_RESUMING + sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_STARTING return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil } @@ -380,7 +380,7 @@ func TestSandboxDelete_NotFound(t *testing.T) { assert.True(t, IsNotFound(err)) } -func TestSandboxSuspendAndResume(t *testing.T) { +func TestSandboxStopAndStart(t *testing.T) { mock := newMockSandboxServer() mock.sandboxes["lifecycle"] = &pb.Sandbox{ Metadata: &dm.ObjectMeta{Name: "lifecycle", Workspace: "team-a"}, @@ -389,13 +389,13 @@ func TestSandboxSuspendAndResume(t *testing.T) { client, cleanup := setupSandboxTest(t, mock) defer cleanup() - suspended, err := client.Suspend(context.Background(), "team-a", "lifecycle") + stopped, err := client.Stop(context.Background(), "team-a", "lifecycle") require.NoError(t, err) - assert.Equal(t, SandboxSuspended, suspended.Status.Phase) + assert.Equal(t, SandboxStopped, stopped.Status.Phase) - resuming, err := client.Resume(context.Background(), "team-a", "lifecycle") + starting, err := client.Start(context.Background(), "team-a", "lifecycle") require.NoError(t, err) - assert.Equal(t, SandboxResuming, resuming.Status.Phase) + assert.Equal(t, SandboxStarting, starting.Status.Phase) } // --- T030: AttachProvider, DetachProvider, ListProviders tests --- @@ -512,18 +512,18 @@ func TestSandboxListProviders_Error(t *testing.T) { // --- T031: WaitReady tests --- -func TestSandboxWaitSuspended_AlreadySuspended(t *testing.T) { +func TestSandboxWaitStopped_AlreadyStopped(t *testing.T) { mock := newMockSandboxServer() mock.sandboxes["sleeping"] = &pb.Sandbox{ Metadata: &dm.ObjectMeta{Name: "sleeping"}, - Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_SUSPENDED}, + Status: &pb.SandboxStatus{Phase: pb.SandboxPhase_SANDBOX_PHASE_STOPPED}, } client, cleanup := setupSandboxTest(t, mock) defer cleanup() - result, err := client.WaitSuspended(context.Background(), "default", "sleeping") + result, err := client.WaitStopped(context.Background(), "default", "sleeping") require.NoError(t, err) - assert.Equal(t, SandboxSuspended, result.Status.Phase) + assert.Equal(t, SandboxStopped, result.Status.Phase) } func TestSandboxWaitReady_AlreadyReady(t *testing.T) { diff --git a/sdk/go/openshell/v1/types.go b/sdk/go/openshell/v1/types.go index 955b1d4cfc..59229ac0b1 100644 --- a/sdk/go/openshell/v1/types.go +++ b/sdk/go/openshell/v1/types.go @@ -17,9 +17,9 @@ const ( SandboxError = types.SandboxError SandboxDeleting = types.SandboxDeleting SandboxUnknown = types.SandboxUnknown - SandboxSuspending = types.SandboxSuspending - SandboxSuspended = types.SandboxSuspended - SandboxResuming = types.SandboxResuming + SandboxStopping = types.SandboxStopping + SandboxStopped = types.SandboxStopped + SandboxStarting = types.SandboxStarting ) // EventType classifies watch events. diff --git a/sdk/go/openshell/v1/types/types.go b/sdk/go/openshell/v1/types/types.go index 7e6190c060..53ccd94a1c 100644 --- a/sdk/go/openshell/v1/types/types.go +++ b/sdk/go/openshell/v1/types/types.go @@ -15,9 +15,9 @@ const ( SandboxError SandboxPhase = "Error" SandboxDeleting SandboxPhase = "Deleting" SandboxUnknown SandboxPhase = "Unknown" - SandboxSuspending SandboxPhase = "Suspending" - SandboxSuspended SandboxPhase = "Suspended" - SandboxResuming SandboxPhase = "Resuming" + SandboxStopping SandboxPhase = "Stopping" + SandboxStopped SandboxPhase = "Stopped" + SandboxStarting SandboxPhase = "Starting" ) // EventType classifies watch events. diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index eb64d75103..fdbcadea97 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -41,9 +41,9 @@ const ( SandboxPhase_SANDBOX_PHASE_ERROR SandboxPhase = 3 SandboxPhase_SANDBOX_PHASE_DELETING SandboxPhase = 4 SandboxPhase_SANDBOX_PHASE_UNKNOWN SandboxPhase = 5 - SandboxPhase_SANDBOX_PHASE_SUSPENDING SandboxPhase = 6 - SandboxPhase_SANDBOX_PHASE_SUSPENDED SandboxPhase = 7 - SandboxPhase_SANDBOX_PHASE_RESUMING SandboxPhase = 8 + SandboxPhase_SANDBOX_PHASE_STOPPING SandboxPhase = 6 + SandboxPhase_SANDBOX_PHASE_STOPPED SandboxPhase = 7 + SandboxPhase_SANDBOX_PHASE_STARTING SandboxPhase = 8 ) // Enum value maps for SandboxPhase. @@ -55,9 +55,9 @@ var ( 3: "SANDBOX_PHASE_ERROR", 4: "SANDBOX_PHASE_DELETING", 5: "SANDBOX_PHASE_UNKNOWN", - 6: "SANDBOX_PHASE_SUSPENDING", - 7: "SANDBOX_PHASE_SUSPENDED", - 8: "SANDBOX_PHASE_RESUMING", + 6: "SANDBOX_PHASE_STOPPING", + 7: "SANDBOX_PHASE_STOPPED", + 8: "SANDBOX_PHASE_STARTING", } SandboxPhase_value = map[string]int32{ "SANDBOX_PHASE_UNSPECIFIED": 0, @@ -66,9 +66,9 @@ var ( "SANDBOX_PHASE_ERROR": 3, "SANDBOX_PHASE_DELETING": 4, "SANDBOX_PHASE_UNKNOWN": 5, - "SANDBOX_PHASE_SUSPENDING": 6, - "SANDBOX_PHASE_SUSPENDED": 7, - "SANDBOX_PHASE_RESUMING": 8, + "SANDBOX_PHASE_STOPPING": 6, + "SANDBOX_PHASE_STOPPED": 7, + "SANDBOX_PHASE_STARTING": 8, } ) @@ -2137,8 +2137,8 @@ func (x *DeleteSandboxRequest) GetWorkspace() string { return "" } -// Suspend sandbox request. -type SuspendSandboxRequest struct { +// Stop sandbox request. +type StopSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Sandbox name (canonical lookup key). Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -2148,20 +2148,20 @@ type SuspendSandboxRequest struct { sizeCache protoimpl.SizeCache } -func (x *SuspendSandboxRequest) Reset() { - *x = SuspendSandboxRequest{} +func (x *StopSandboxRequest) Reset() { + *x = StopSandboxRequest{} mi := &file_openshell_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SuspendSandboxRequest) String() string { +func (x *StopSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SuspendSandboxRequest) ProtoMessage() {} +func (*StopSandboxRequest) ProtoMessage() {} -func (x *SuspendSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2173,27 +2173,27 @@ func (x *SuspendSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SuspendSandboxRequest.ProtoReflect.Descriptor instead. -func (*SuspendSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. +func (*StopSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{27} } -func (x *SuspendSandboxRequest) GetName() string { +func (x *StopSandboxRequest) GetName() string { if x != nil { return x.Name } return "" } -func (x *SuspendSandboxRequest) GetWorkspace() string { +func (x *StopSandboxRequest) GetWorkspace() string { if x != nil { return x.Workspace } return "" } -// Resume sandbox request. -type ResumeSandboxRequest struct { +// Start sandbox request. +type StartSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Sandbox name (canonical lookup key). Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -2203,20 +2203,20 @@ type ResumeSandboxRequest struct { sizeCache protoimpl.SizeCache } -func (x *ResumeSandboxRequest) Reset() { - *x = ResumeSandboxRequest{} +func (x *StartSandboxRequest) Reset() { + *x = StartSandboxRequest{} mi := &file_openshell_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ResumeSandboxRequest) String() string { +func (x *StartSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ResumeSandboxRequest) ProtoMessage() {} +func (*StartSandboxRequest) ProtoMessage() {} -func (x *ResumeSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2228,19 +2228,19 @@ func (x *ResumeSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ResumeSandboxRequest.ProtoReflect.Descriptor instead. -func (*ResumeSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StartSandboxRequest.ProtoReflect.Descriptor instead. +func (*StartSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{28} } -func (x *ResumeSandboxRequest) GetName() string { +func (x *StartSandboxRequest) GetName() string { if x != nil { return x.Name } return "" } -func (x *ResumeSandboxRequest) GetWorkspace() string { +func (x *StartSandboxRequest) GetWorkspace() string { if x != nil { return x.Workspace } @@ -13122,11 +13122,11 @@ const file_openshell_proto_rawDesc = "" + "\tworkspace\x18\x04 \x01(\tR\tworkspace\"H\n" + "\x14DeleteSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + - "\tworkspace\x18\x02 \x01(\tR\tworkspace\"I\n" + - "\x15SuspendSandboxRequest\x12\x12\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"F\n" + + "\x12StopSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + - "\tworkspace\x18\x02 \x01(\tR\tworkspace\"H\n" + - "\x14ResumeSandboxRequest\x12\x12\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"G\n" + + "\x13StartSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"B\n" + "\x0fSandboxResponse\x12/\n" + @@ -13971,17 +13971,17 @@ const file_openshell_proto_rawDesc = "" + "\x05limit\x18\x02 \x01(\rR\x05limit\x12\x16\n" + "\x06offset\x18\x03 \x01(\rR\x06offset\"W\n" + "\x1cListWorkspaceMembersResponse\x127\n" + - "\amembers\x18\x01 \x03(\v2\x1d.openshell.v1.WorkspaceMemberR\amembers*\x8d\x02\n" + + "\amembers\x18\x01 \x03(\v2\x1d.openshell.v1.WorkspaceMemberR\amembers*\x89\x02\n" + "\fSandboxPhase\x12\x1d\n" + "\x19SANDBOX_PHASE_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aSANDBOX_PHASE_PROVISIONING\x10\x01\x12\x17\n" + "\x13SANDBOX_PHASE_READY\x10\x02\x12\x17\n" + "\x13SANDBOX_PHASE_ERROR\x10\x03\x12\x1a\n" + "\x16SANDBOX_PHASE_DELETING\x10\x04\x12\x19\n" + - "\x15SANDBOX_PHASE_UNKNOWN\x10\x05\x12\x1c\n" + - "\x18SANDBOX_PHASE_SUSPENDING\x10\x06\x12\x1b\n" + - "\x17SANDBOX_PHASE_SUSPENDED\x10\a\x12\x1a\n" + - "\x16SANDBOX_PHASE_RESUMING\x10\b*\xc3\x03\n" + + "\x15SANDBOX_PHASE_UNKNOWN\x10\x05\x12\x1a\n" + + "\x16SANDBOX_PHASE_STOPPING\x10\x06\x12\x19\n" + + "\x15SANDBOX_PHASE_STOPPED\x10\a\x12\x1a\n" + + "\x16SANDBOX_PHASE_STARTING\x10\b*\xc3\x03\n" + "!ProviderCredentialRefreshStrategy\x124\n" + "0PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED\x10\x00\x12/\n" + "+PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC\x10\x01\x121\n" + @@ -14013,7 +14013,7 @@ const file_openshell_proto_rawDesc = "" + "\rWorkspaceRole\x12\x1e\n" + "\x1aWORKSPACE_ROLE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13WORKSPACE_ROLE_USER\x10\x01\x12\x18\n" + - "\x14WORKSPACE_ROLE_ADMIN\x10\x022\x9cD\n" + + "\x14WORKSPACE_ROLE_ADMIN\x10\x022\x94D\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -14035,10 +14035,10 @@ const file_openshell_proto_rawDesc = "" + "\x15DetachSandboxProvider\x12*.openshell.v1.DetachSandboxProviderRequest\x1a+.openshell.v1.DetachSandboxProviderResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12{\n" + "\rDeleteSandbox\x12\".openshell.v1.DeleteSandboxRequest\x1a#.openshell.v1.DeleteSandboxResponse\"!\x82\xb5\x18\x1d\n" + - "\x06bearer\x12\x04user\"\rsandbox:write\x12w\n" + - "\x0eSuspendSandbox\x12#.openshell.v1.SuspendSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + - "\x06bearer\x12\x04user\"\rsandbox:write\x12u\n" + - "\rResumeSandbox\x12\".openshell.v1.ResumeSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12q\n" + + "\vStopSandbox\x12 .openshell.v1.StopSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12s\n" + + "\fStartSandbox\x12!.openshell.v1.StartSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12\x84\x01\n" + "\x10CreateSshSession\x12%.openshell.v1.CreateSshSessionRequest\x1a&.openshell.v1.CreateSshSessionResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12}\n" + @@ -14199,8 +14199,8 @@ var file_openshell_proto_goTypes = []any{ (*AttachSandboxProviderRequest)(nil), // 30: openshell.v1.AttachSandboxProviderRequest (*DetachSandboxProviderRequest)(nil), // 31: openshell.v1.DetachSandboxProviderRequest (*DeleteSandboxRequest)(nil), // 32: openshell.v1.DeleteSandboxRequest - (*SuspendSandboxRequest)(nil), // 33: openshell.v1.SuspendSandboxRequest - (*ResumeSandboxRequest)(nil), // 34: openshell.v1.ResumeSandboxRequest + (*StopSandboxRequest)(nil), // 33: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 34: openshell.v1.StartSandboxRequest (*SandboxResponse)(nil), // 35: openshell.v1.SandboxResponse (*ListSandboxesResponse)(nil), // 36: openshell.v1.ListSandboxesResponse (*ListSandboxProvidersResponse)(nil), // 37: openshell.v1.ListSandboxProvidersResponse @@ -14557,8 +14557,8 @@ var file_openshell_proto_depIdxs = []int32{ 30, // 157: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest 31, // 158: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest 32, // 159: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 33, // 160: openshell.v1.OpenShell.SuspendSandbox:input_type -> openshell.v1.SuspendSandboxRequest - 34, // 161: openshell.v1.OpenShell.ResumeSandbox:input_type -> openshell.v1.ResumeSandboxRequest + 33, // 160: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 34, // 161: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest 41, // 162: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest 43, // 163: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest 44, // 164: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest @@ -14623,8 +14623,8 @@ var file_openshell_proto_depIdxs = []int32{ 38, // 223: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse 39, // 224: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse 40, // 225: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 35, // 226: openshell.v1.OpenShell.SuspendSandbox:output_type -> openshell.v1.SandboxResponse - 35, // 227: openshell.v1.OpenShell.ResumeSandbox:output_type -> openshell.v1.SandboxResponse + 35, // 226: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 35, // 227: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse 42, // 228: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse 50, // 229: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse 50, // 230: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go index ef4b4c2669..92c94ef299 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -33,8 +33,8 @@ const ( OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" - OpenShell_SuspendSandbox_FullMethodName = "/openshell.v1.OpenShell/SuspendSandbox" - OpenShell_ResumeSandbox_FullMethodName = "/openshell.v1.OpenShell/ResumeSandbox" + OpenShell_StopSandbox_FullMethodName = "/openshell.v1.OpenShell/StopSandbox" + OpenShell_StartSandbox_FullMethodName = "/openshell.v1.OpenShell/StartSandbox" OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" @@ -124,10 +124,10 @@ type OpenShellClient interface { DetachSandboxProvider(ctx context.Context, in *DetachSandboxProviderRequest, opts ...grpc.CallOption) (*DetachSandboxProviderResponse, error) // Delete a sandbox by name. DeleteSandbox(ctx context.Context, in *DeleteSandboxRequest, opts ...grpc.CallOption) (*DeleteSandboxResponse, error) - // Suspend a sandbox while retaining its persistent state. - SuspendSandbox(ctx context.Context, in *SuspendSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) - // Resume a previously suspended sandbox. - ResumeSandbox(ctx context.Context, in *ResumeSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) + // Stop a sandbox while retaining its persistent state. + StopSandbox(ctx context.Context, in *StopSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) + // Start a previously stopped sandbox. + StartSandbox(ctx context.Context, in *StartSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) // Create a short-lived SSH session for a sandbox. CreateSshSession(ctx context.Context, in *CreateSshSessionRequest, opts ...grpc.CallOption) (*CreateSshSessionResponse, error) // Create or update a sandbox HTTP service endpoint for local routing. @@ -385,20 +385,20 @@ func (c *openShellClient) DeleteSandbox(ctx context.Context, in *DeleteSandboxRe return out, nil } -func (c *openShellClient) SuspendSandbox(ctx context.Context, in *SuspendSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { +func (c *openShellClient) StopSandbox(ctx context.Context, in *StopSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SandboxResponse) - err := c.cc.Invoke(ctx, OpenShell_SuspendSandbox_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, OpenShell_StopSandbox_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *openShellClient) ResumeSandbox(ctx context.Context, in *ResumeSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { +func (c *openShellClient) StartSandbox(ctx context.Context, in *StartSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SandboxResponse) - err := c.cc.Invoke(ctx, OpenShell_ResumeSandbox_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, OpenShell_StartSandbox_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -1011,10 +1011,10 @@ type OpenShellServer interface { DetachSandboxProvider(context.Context, *DetachSandboxProviderRequest) (*DetachSandboxProviderResponse, error) // Delete a sandbox by name. DeleteSandbox(context.Context, *DeleteSandboxRequest) (*DeleteSandboxResponse, error) - // Suspend a sandbox while retaining its persistent state. - SuspendSandbox(context.Context, *SuspendSandboxRequest) (*SandboxResponse, error) - // Resume a previously suspended sandbox. - ResumeSandbox(context.Context, *ResumeSandboxRequest) (*SandboxResponse, error) + // Stop a sandbox while retaining its persistent state. + StopSandbox(context.Context, *StopSandboxRequest) (*SandboxResponse, error) + // Start a previously stopped sandbox. + StartSandbox(context.Context, *StartSandboxRequest) (*SandboxResponse, error) // Create a short-lived SSH session for a sandbox. CreateSshSession(context.Context, *CreateSshSessionRequest) (*CreateSshSessionResponse, error) // Create or update a sandbox HTTP service endpoint for local routing. @@ -1202,11 +1202,11 @@ func (UnimplementedOpenShellServer) DetachSandboxProvider(context.Context, *Deta func (UnimplementedOpenShellServer) DeleteSandbox(context.Context, *DeleteSandboxRequest) (*DeleteSandboxResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteSandbox not implemented") } -func (UnimplementedOpenShellServer) SuspendSandbox(context.Context, *SuspendSandboxRequest) (*SandboxResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SuspendSandbox not implemented") +func (UnimplementedOpenShellServer) StopSandbox(context.Context, *StopSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method StopSandbox not implemented") } -func (UnimplementedOpenShellServer) ResumeSandbox(context.Context, *ResumeSandboxRequest) (*SandboxResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ResumeSandbox not implemented") +func (UnimplementedOpenShellServer) StartSandbox(context.Context, *StartSandboxRequest) (*SandboxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method StartSandbox not implemented") } func (UnimplementedOpenShellServer) CreateSshSession(context.Context, *CreateSshSessionRequest) (*CreateSshSessionResponse, error) { return nil, status.Error(codes.Unimplemented, "method CreateSshSession not implemented") @@ -1571,38 +1571,38 @@ func _OpenShell_DeleteSandbox_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } -func _OpenShell_SuspendSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SuspendSandboxRequest) +func _OpenShell_StopSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StopSandboxRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(OpenShellServer).SuspendSandbox(ctx, in) + return srv.(OpenShellServer).StopSandbox(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: OpenShell_SuspendSandbox_FullMethodName, + FullMethod: OpenShell_StopSandbox_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(OpenShellServer).SuspendSandbox(ctx, req.(*SuspendSandboxRequest)) + return srv.(OpenShellServer).StopSandbox(ctx, req.(*StopSandboxRequest)) } return interceptor(ctx, in, info, handler) } -func _OpenShell_ResumeSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ResumeSandboxRequest) +func _OpenShell_StartSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StartSandboxRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(OpenShellServer).ResumeSandbox(ctx, in) + return srv.(OpenShellServer).StartSandbox(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: OpenShell_ResumeSandbox_FullMethodName, + FullMethod: OpenShell_StartSandbox_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(OpenShellServer).ResumeSandbox(ctx, req.(*ResumeSandboxRequest)) + return srv.(OpenShellServer).StartSandbox(ctx, req.(*StartSandboxRequest)) } return interceptor(ctx, in, info, handler) } @@ -2558,12 +2558,12 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ Handler: _OpenShell_DeleteSandbox_Handler, }, { - MethodName: "SuspendSandbox", - Handler: _OpenShell_SuspendSandbox_Handler, + MethodName: "StopSandbox", + Handler: _OpenShell_StopSandbox_Handler, }, { - MethodName: "ResumeSandbox", - Handler: _OpenShell_ResumeSandbox_Handler, + MethodName: "StartSandbox", + Handler: _OpenShell_StartSandbox_Handler, }, { MethodName: "CreateSshSession", From 45a8c7fdac2c7ea2cfe10a5f79099385979c56c8 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Tue, 11 Aug 2026 21:50:18 -0500 Subject: [PATCH 14/15] perf(server): clean stopped sessions on transition Signed-off-by: Seth Jennings --- crates/openshell-server/src/compute/mod.rs | 57 +++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 94308f88f5..9742869027 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -2582,7 +2582,9 @@ impl ComputeRuntime { return Ok(()); } - self.update_sandbox_record(incoming, existing_record.resource_version) + let existing_phase = + SandboxPhase::try_from(existing.phase()).unwrap_or(SandboxPhase::Unknown); + self.update_sandbox_record(incoming, existing_record.resource_version, existing_phase) .await } @@ -2592,6 +2594,7 @@ impl ComputeRuntime { &self, incoming: DriverSandbox, expected_resource_version: u64, + existing_phase: SandboxPhase, ) -> Result<(), String> { let session_connected = self.supervisor_sessions.has_session(&incoming.id); let sandbox = self @@ -2615,7 +2618,9 @@ impl ComputeRuntime { self.sandbox_index.update_from_sandbox(&sandbox); self.sandbox_watch_bus.notify(sandbox.object_id()); - if sandbox.phase() == SandboxPhase::Stopped as i32 { + if existing_phase != SandboxPhase::Stopped + && sandbox.phase() == SandboxPhase::Stopped as i32 + { self.cleanup_stopped_sandbox_sessions(&sandbox).await?; } Ok(()) @@ -5697,6 +5702,54 @@ mod tests { assert_eq!(current.phase(), SandboxPhase::Stopped as i32); } + #[tokio::test] + async fn repeated_stopped_snapshot_does_not_repeat_session_cleanup() { + let runtime = test_runtime(Arc::new(TestDriver::default())).await; + let sandbox = sandbox_record("sb-stopping", "sandbox-stopping", SandboxPhase::Stopping); + runtime.store.put_message(&sandbox).await.unwrap(); + let transition_session = ssh_session_record("transition-session", sandbox.object_id()); + runtime + .store + .put_message(&transition_session) + .await + .unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + + let mut stopped = ready_driver_sandbox(sandbox.object_id(), sandbox.object_name()); + stopped.status = Some(make_driver_status(make_driver_condition( + "ContainerExited", + "container stopped by request", + ))); + runtime.apply_sandbox_update(stopped.clone()).await.unwrap(); + + assert!(!runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(transition_session.object_id()) + .await + .unwrap() + .is_none(), + "the transition to stopped cleans up ephemeral SSH sessions" + ); + + let later_session = ssh_session_record("later-session", sandbox.object_id()); + runtime.store.put_message(&later_session).await.unwrap(); + register_test_supervisor_session(&runtime, sandbox.object_id()); + runtime.apply_sandbox_update(stopped).await.unwrap(); + + assert!(runtime.supervisor_sessions.has_session(sandbox.object_id())); + assert!( + runtime + .store + .get_message::(later_session.object_id()) + .await + .unwrap() + .is_some(), + "later stopped snapshots do not repeat session cleanup" + ); + } + #[tokio::test] async fn stopped_container_snapshot_confirms_stopping_sandbox() { let runtime = test_runtime(Arc::new(TestDriver::default())).await; From 002b3d547a91ecc3de13c86ff05c514e2e9e946c Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Tue, 11 Aug 2026 21:52:25 -0500 Subject: [PATCH 15/15] fix(kubernetes): fail fast on rejected stop Signed-off-by: Seth Jennings --- .../openshell-driver-kubernetes/src/driver.rs | 70 ++++++++++++++++++- crates/openshell-server/src/compute/mod.rs | 6 +- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 478297b82d..00e38f6dd2 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -102,6 +102,8 @@ const SANDBOX_VERSION_V1ALPHA1: &str = "v1alpha1"; const SANDBOX_VERSIONS: &[&str] = &[SANDBOX_VERSION_V1BETA1, SANDBOX_VERSION_V1ALPHA1]; pub const SANDBOX_KIND: &str = "Sandbox"; const SANDBOX_POD_NAME_ANNOTATION: &str = "agents.x-k8s.io/pod-name"; +const SANDBOX_SUSPENDED_CONDITION: &str = "Suspended"; +const SANDBOX_SUSPENDED_POD_NOT_OWNED_REASON: &str = "PodNotOwned"; const GPU_RESOURCE_NAME: &str = "nvidia.com/gpu"; const SPIFFE_WORKLOAD_API_VOLUME_NAME: &str = "spiffe-workload-api"; @@ -970,6 +972,9 @@ impl KubernetesComputeDriver { if kubernetes_sandbox_has_stopped_condition(&object) { return Ok(()); } + if let Some(error) = kubernetes_sandbox_stop_failure(&object) { + return Err(error); + } if let Some(pod_api) = legacy_pod_api.as_ref() && kubernetes_sandbox_pod_is_gone(pod_api, &pod_name, deadline).await? { @@ -3267,7 +3272,8 @@ fn kubernetes_sandbox_has_stopped_condition(obj: &DynamicObject) -> bool { .and_then(serde_json::Value::as_array) .is_some_and(|conditions| { conditions.iter().any(|condition| { - condition.get("type").and_then(serde_json::Value::as_str) == Some("Suspended") + condition.get("type").and_then(serde_json::Value::as_str) + == Some(SANDBOX_SUSPENDED_CONDITION) && condition .get("status") .and_then(serde_json::Value::as_str) @@ -3276,6 +3282,34 @@ fn kubernetes_sandbox_has_stopped_condition(obj: &DynamicObject) -> bool { }) } +fn kubernetes_sandbox_stop_failure(obj: &DynamicObject) -> Option { + obj.data + .get("status")? + .get("conditions")? + .as_array()? + .iter() + .find_map(|condition| { + let is_terminal = condition.get("type").and_then(serde_json::Value::as_str) + == Some(SANDBOX_SUSPENDED_CONDITION) + && condition + .get("status") + .and_then(serde_json::Value::as_str) + .is_some_and(|status| status.eq_ignore_ascii_case("false")) + && condition.get("reason").and_then(serde_json::Value::as_str) + == Some(SANDBOX_SUSPENDED_POD_NOT_OWNED_REASON); + if !is_terminal { + return None; + } + + let message = condition + .get("message") + .and_then(serde_json::Value::as_str) + .filter(|message| !message.is_empty()) + .unwrap_or("backing pod is not owned by this sandbox"); + Some(format!("Kubernetes sandbox stop rejected: {message}")) + }) +} + async fn kubernetes_sandbox_pod_is_gone( pod_api: &Api, pod_name: &str, @@ -3489,6 +3523,40 @@ mod tests { assert!(kubernetes_sandbox_has_stopped_condition(&sandbox)); } + #[test] + fn stop_failure_only_rejects_terminal_suspension_condition() { + let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( + SANDBOX_GROUP, + SANDBOX_VERSION_V1BETA1, + SANDBOX_KIND, + )); + let mut sandbox = DynamicObject::new("sandbox", &resource); + sandbox.data = serde_json::json!({ + "status": { + "conditions": [{ + "type": "Suspended", + "status": "False", + "reason": "PodNotOwned", + "message": "Refused to delete pod because it is not owned by this sandbox" + }] + } + }); + + assert_eq!( + kubernetes_sandbox_stop_failure(&sandbox).as_deref(), + Some( + "Kubernetes sandbox stop rejected: Refused to delete pod because it is not owned by this sandbox" + ) + ); + + sandbox.data["status"]["conditions"][0]["status"] = serde_json::json!("Unknown"); + sandbox.data["status"]["conditions"][0]["reason"] = serde_json::json!("PodStateUnknown"); + assert!( + kubernetes_sandbox_stop_failure(&sandbox).is_none(), + "an unknown pod state can recover on a later controller reconciliation" + ); + } + #[test] fn sandbox_api_version_probe_keeps_non_404_errors() { let err = kube_api_error(403, "sandboxes.agents.x-k8s.io is forbidden"); diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 9742869027..cbe472dc39 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -3523,7 +3523,7 @@ fn driver_snapshot_confirms_stopped(incoming: &DriverSandbox) -> bool { fn driver_snapshot_confirms_stopping(incoming: &DriverSandbox) -> bool { incoming.status.as_ref().is_some_and(|status| { status.conditions.iter().any(|condition| { - condition.r#type.eq_ignore_ascii_case("Stopped") + condition.r#type.eq_ignore_ascii_case("Suspended") && condition.status.eq_ignore_ascii_case("false") && matches!( condition.reason.to_ascii_lowercase().as_str(), @@ -3671,7 +3671,7 @@ fn derive_phase(status: Option<&DriverSandboxStatus>) -> SandboxPhase { } if status.conditions.iter().any(|condition| { - condition.r#type.eq_ignore_ascii_case("Stopped") + condition.r#type.eq_ignore_ascii_case("Suspended") && condition.status.eq_ignore_ascii_case("true") }) { return SandboxPhase::Stopped; @@ -5574,7 +5574,7 @@ mod tests { instance_id: format!("{}-pod", sandbox.object_name()), conditions: vec![ DriverCondition { - r#type: "Stopped".to_string(), + r#type: "Suspended".to_string(), status: "False".to_string(), reason: "PodTerminating".to_string(), message: "Pod is terminating. Sandbox is stopping".to_string(),