Skip to content

Commit e882ac0

Browse files
committed
fix(server): restore sandbox launch sessions
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 707cf06 commit e882ac0

2 files changed

Lines changed: 107 additions & 3 deletions

File tree

crates/openshell-server/src/compute/mod.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2293,6 +2293,21 @@ impl ComputeRuntime {
22932293
/// Should be called once at gateway startup, before watchers spawn,
22942294
/// so the watch loop sees the post-start state on its first poll.
22952295
pub async fn start_persisted_sandboxes(&self) -> Result<(), String> {
2296+
self.start_persisted_sandboxes_with_authentication(|_| Ok(Vec::new()), |_| {})
2297+
.await
2298+
}
2299+
2300+
/// Reconcile persisted running intent and provision fresh launch
2301+
/// authentication before a restored runtime reconnects.
2302+
pub async fn start_persisted_sandboxes_with_authentication<Authentication, Failed>(
2303+
&self,
2304+
launch_authentication_for: Authentication,
2305+
authentication_failed: Failed,
2306+
) -> Result<(), String>
2307+
where
2308+
Authentication: Fn(&Sandbox) -> Result<Vec<u8>, String>,
2309+
Failed: Fn(&str),
2310+
{
22962311
self.recover_persisted_lifecycle_transitions().await?;
22972312
if !self.driver_info.gateway_manages_lifecycle {
22982313
return Ok(());
@@ -2333,6 +2348,29 @@ impl ComputeRuntime {
23332348
sandbox_resource_version(&sandbox),
23342349
)
23352350
.into_string();
2351+
let launch_authentication = match launch_authentication_for(&sandbox) {
2352+
Ok(authentication) => authentication,
2353+
Err(err) => {
2354+
warn!(
2355+
sandbox_id = %sandbox.object_id(),
2356+
sandbox_name = %sandbox.object_name(),
2357+
error = %err,
2358+
"Failed to prepare sandbox authentication during gateway startup"
2359+
);
2360+
if !recoverable_error {
2361+
self.mark_sandbox_error(
2362+
&sandbox,
2363+
"AuthenticationFailed",
2364+
&format!(
2365+
"Failed to prepare sandbox authentication during gateway startup: {err}"
2366+
),
2367+
)
2368+
.await;
2369+
}
2370+
failed += 1;
2371+
continue;
2372+
}
2373+
};
23362374
match self
23372375
.driver
23382376
.call(
@@ -2341,12 +2379,13 @@ impl ComputeRuntime {
23412379
|driver| {
23422380
let sandbox_id = sandbox_id.clone();
23432381
let sandbox_name = sandbox_name.clone();
2382+
let launch_authentication = launch_authentication.clone();
23442383
async move {
23452384
driver
23462385
.start_sandbox(Request::new(StartSandboxRequest {
23472386
sandbox_id,
23482387
sandbox_name,
2349-
launch_authentication: Vec::new(),
2388+
launch_authentication,
23502389
generation_id,
23512390
}))
23522391
.await
@@ -2374,6 +2413,7 @@ impl ComputeRuntime {
23742413
}
23752414
}
23762415
Err(err) if err.code() == Code::NotFound => {
2416+
authentication_failed(sandbox.object_id());
23772417
// Backend resource is gone but the store still
23782418
// remembers the sandbox. Mark Error so the UI
23792419
// surfaces the inconsistency; the reconcile loop
@@ -2395,6 +2435,7 @@ impl ComputeRuntime {
23952435
missing += 1;
23962436
}
23972437
Err(err) => {
2438+
authentication_failed(sandbox.object_id());
23982439
warn!(
23992440
sandbox_id = %sandbox.object_id(),
24002441
sandbox_name = %sandbox.object_name(),
@@ -5552,6 +5593,7 @@ mod tests {
55525593
start_blocked: AtomicBool,
55535594
start_calls: AtomicUsize,
55545595
start_requests: TestMutex<Vec<(String, String)>>,
5596+
start_authentications: TestMutex<Vec<Vec<u8>>>,
55555597
start_outcome: TestMutex<ControlledLifecycleOutcome>,
55565598
get_started: Notify,
55575599
get_release: Semaphore,
@@ -5585,6 +5627,7 @@ mod tests {
55855627
start_blocked: AtomicBool::new(false),
55865628
start_calls: AtomicUsize::new(0),
55875629
start_requests: TestMutex::new(Vec::new()),
5630+
start_authentications: TestMutex::new(Vec::new()),
55885631
start_outcome: TestMutex::new(ControlledLifecycleOutcome::Ok),
55895632
get_started: Notify::new(),
55905633
get_release: Semaphore::new(0),
@@ -5683,6 +5726,13 @@ mod tests {
56835726
.clone()
56845727
}
56855728

5729+
fn start_authentications(&self) -> Vec<Vec<u8>> {
5730+
self.start_authentications
5731+
.lock()
5732+
.expect("start authentications lock poisoned")
5733+
.clone()
5734+
}
5735+
56865736
fn send_event(&self, event: WatchSandboxesEvent) {
56875737
self.watch_tx
56885738
.send(Ok(event))
@@ -5828,6 +5878,10 @@ mod tests {
58285878
.lock()
58295879
.expect("start requests lock poisoned")
58305880
.push((request.sandbox_id, request.sandbox_name));
5881+
self.start_authentications
5882+
.lock()
5883+
.expect("start authentications lock poisoned")
5884+
.push(request.launch_authentication);
58315885
self.start_calls.fetch_add(1, Ordering::SeqCst);
58325886
self.start_started.notify_one();
58335887
if self.start_blocked.load(Ordering::SeqCst) {
@@ -10872,6 +10926,31 @@ mod tests {
1087210926
);
1087310927
}
1087410928

10929+
#[tokio::test]
10930+
async fn start_persisted_sandboxes_supplies_fresh_authentication() {
10931+
let driver = ControlledDriver::new();
10932+
let runtime =
10933+
test_runtime_with_gateway_managed_lifecycle(driver.clone(), "arbitrary").await;
10934+
runtime
10935+
.store
10936+
.put_message(&sandbox_record("sb-1", "sandbox", SandboxPhase::Ready))
10937+
.await
10938+
.unwrap();
10939+
10940+
runtime
10941+
.start_persisted_sandboxes_with_authentication(
10942+
|sandbox| Ok(format!("authentication:{}", sandbox.object_id()).into_bytes()),
10943+
|_| {},
10944+
)
10945+
.await
10946+
.unwrap();
10947+
10948+
assert_eq!(
10949+
driver.start_authentications(),
10950+
vec![b"authentication:sb-1".to_vec()]
10951+
);
10952+
}
10953+
1087510954
#[tokio::test]
1087610955
async fn startup_sweep_rechecks_intent_after_acquiring_gate() {
1087710956
let driver = ControlledDriver::new();

crates/openshell-server/src/lib.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ mod ws_tunnel;
5151
use metrics_exporter_prometheus::PrometheusBuilder;
5252
use openshell_core::net::set_tcp_nodelay_best_effort;
5353
use openshell_core::telemetry::TelemetryComputeDriver;
54-
use openshell_core::{Config, Error, ObjectLabels, Result};
54+
use openshell_core::{Config, Error, ObjectId, ObjectLabels, Result};
5555
use openshell_extension_core::{
5656
BearerTokenSlot, ExtensionAudience, ExtensionCallerKind, ExtensionKind, MAX_EXTENSION_TOKEN_TTL,
5757
};
@@ -814,7 +814,32 @@ pub(crate) async fn run_server(
814814
// driver reconciles persisted sandboxes. Serve them before starting that
815815
// reconciliation so policy fetch and supervisor-session registration
816816
// cannot deadlock gateway startup.
817-
if let Err(err) = state.compute.start_persisted_sandboxes().await {
817+
if let Err(err) = state
818+
.compute
819+
.start_persisted_sandboxes_with_authentication(
820+
|sandbox| {
821+
let Some(authority) = &state.sandbox_session_jwt_authority else {
822+
return Ok(Vec::new());
823+
};
824+
let authentication = authority
825+
.mint_launch(
826+
sandbox.object_id(),
827+
openshell_core::SandboxSessionId::new(),
828+
openshell_core::jwt::CredentialEpoch::new(1)
829+
.map_err(|error| error.to_string())?,
830+
)
831+
.map_err(|error| error.to_string())?;
832+
state
833+
.sandbox_auth_sessions
834+
.activate(sandbox.object_id(), &authentication, authority)
835+
.map_err(|error| error.to_string())?;
836+
serde_json::to_vec(&authentication)
837+
.map_err(|error| format!("encode launch authentication: {error}"))
838+
},
839+
|sandbox_id| state.sandbox_auth_sessions.deactivate(sandbox_id),
840+
)
841+
.await
842+
{
818843
warn!(error = %err, "Failed to start persisted sandboxes during startup");
819844
}
820845

0 commit comments

Comments
 (0)