@@ -36,15 +36,20 @@ use openshell_driver_kubernetes::{
3636use openshell_driver_podman:: {
3737 ComputeDriverService as PodmanDriverService , PodmanComputeConfig , PodmanComputeDriver ,
3838} ;
39+ use hyper_util:: rt:: TokioIo ;
3940use prost:: Message ;
4041use std:: fmt;
4142use std:: net:: SocketAddr ;
43+ use std:: path:: { Path , PathBuf } ;
4244use std:: pin:: Pin ;
4345use std:: sync:: Arc ;
4446use std:: time:: Duration ;
47+ #[ cfg( unix) ]
48+ use tokio:: net:: UnixStream ;
4549use tokio:: sync:: { Mutex , watch} ;
46- use tonic:: transport:: Channel ;
50+ use tonic:: transport:: { Channel , Endpoint } ;
4751use tonic:: { Code , Request , Status } ;
52+ use tower:: service_fn;
4853use tracing:: { debug, info, warn} ;
4954
5055type DriverWatchStream = Pin < Box < dyn Stream < Item = Result < WatchSandboxesEvent , Status > > + Send > > ;
@@ -102,11 +107,11 @@ pub use openshell_core::ComputeDriverError as ComputeError;
102107#[ derive( Debug ) ]
103108pub struct ManagedDriverProcess {
104109 child : std:: sync:: Mutex < Option < tokio:: process:: Child > > ,
105- socket_path : std :: path :: PathBuf ,
110+ socket_path : PathBuf ,
106111}
107112
108113impl ManagedDriverProcess {
109- pub ( crate ) fn new ( child : tokio:: process:: Child , socket_path : std :: path :: PathBuf ) -> Self {
114+ pub ( crate ) fn new ( child : tokio:: process:: Child , socket_path : PathBuf ) -> Self {
110115 Self {
111116 child : std:: sync:: Mutex :: new ( Some ( child) ) ,
112117 socket_path,
@@ -245,7 +250,7 @@ impl fmt::Debug for ComputeRuntime {
245250impl ComputeRuntime {
246251 #[ allow( clippy:: too_many_arguments) ]
247252 async fn from_driver (
248- driver_kind : ComputeDriverKind ,
253+ driver_kind : Option < ComputeDriverKind > ,
249254 driver : SharedComputeDriver ,
250255 shutdown_cleanup : Option < Arc < dyn ShutdownCleanup > > ,
251256 startup_resume : Option < Arc < dyn StartupResume > > ,
@@ -258,15 +263,24 @@ impl ComputeRuntime {
258263 _allows_loopback_endpoints : bool ,
259264 gateway_bind_addresses : Vec < SocketAddr > ,
260265 ) -> Result < Self , ComputeError > {
261- let default_image = driver
266+ let capabilities = driver
262267 . get_capabilities ( Request :: new ( GetCapabilitiesRequest { } ) )
263268 . await
264269 . map_err ( compute_error_from_status) ?
265- . into_inner ( )
266- . default_image ;
270+ . into_inner ( ) ;
271+ // For out-of-tree drivers (driver_kind = None), log the name the
272+ // driver advertises in GetCapabilities so operators can confirm
273+ // the gateway is talking to the driver they expect.
274+ if driver_kind. is_none ( ) {
275+ info ! (
276+ driver_name = %capabilities. driver_name,
277+ "External compute driver connected"
278+ ) ;
279+ }
280+ let default_image = capabilities. default_image ;
267281 Ok ( Self {
268282 driver,
269- driver_kind : Some ( driver_kind ) ,
283+ driver_kind,
270284 shutdown_cleanup,
271285 startup_resume,
272286 _driver_process : driver_process,
@@ -311,7 +325,7 @@ impl ComputeRuntime {
311325 let startup_resume: Arc < dyn StartupResume > = driver. clone ( ) ;
312326 let driver: SharedComputeDriver = driver;
313327 Self :: from_driver (
314- ComputeDriverKind :: Docker ,
328+ Some ( ComputeDriverKind :: Docker ) ,
315329 driver,
316330 Some ( shutdown_cleanup) ,
317331 Some ( startup_resume) ,
@@ -340,7 +354,7 @@ impl ComputeRuntime {
340354 . map_err ( |err| ComputeError :: Message ( err. to_string ( ) ) ) ?;
341355 let driver: SharedComputeDriver = Arc :: new ( ComputeDriverService :: new ( driver) ) ;
342356 Self :: from_driver (
343- ComputeDriverKind :: Kubernetes ,
357+ Some ( ComputeDriverKind :: Kubernetes ) ,
344358 driver,
345359 None ,
346360 None ,
@@ -367,7 +381,7 @@ impl ComputeRuntime {
367381 ) -> Result < Self , ComputeError > {
368382 let driver: SharedComputeDriver = Arc :: new ( RemoteComputeDriver :: new ( channel) ) ;
369383 Self :: from_driver (
370- ComputeDriverKind :: Vm ,
384+ Some ( ComputeDriverKind :: Vm ) ,
371385 driver,
372386 None ,
373387 None ,
@@ -383,6 +397,39 @@ impl ComputeRuntime {
383397 . await
384398 }
385399
400+ /// Construct a runtime that proxies all sandbox lifecycle to an
401+ /// out-of-tree compute driver listening on a pre-existing UDS endpoint.
402+ ///
403+ /// The driver process is operator-managed (not spawned by the gateway),
404+ /// so no [`ManagedDriverProcess`] handle is attached. The advertised
405+ /// `driver_name` from `GetCapabilities` is logged for diagnostics by
406+ /// [`Self::from_driver`].
407+ pub ( crate ) async fn new_remote_external (
408+ channel : Channel ,
409+ store : Arc < Store > ,
410+ sandbox_index : SandboxIndex ,
411+ sandbox_watch_bus : SandboxWatchBus ,
412+ tracing_log_bus : TracingLogBus ,
413+ supervisor_sessions : Arc < SupervisorSessionRegistry > ,
414+ ) -> Result < Self , ComputeError > {
415+ let driver: SharedComputeDriver = Arc :: new ( RemoteComputeDriver :: new ( channel) ) ;
416+ Self :: from_driver (
417+ None ,
418+ driver,
419+ None ,
420+ None ,
421+ None ,
422+ store,
423+ sandbox_index,
424+ sandbox_watch_bus,
425+ tracing_log_bus,
426+ supervisor_sessions,
427+ true ,
428+ Vec :: new ( ) ,
429+ )
430+ . await
431+ }
432+
386433 pub async fn new_podman (
387434 config : PodmanComputeConfig ,
388435 store : Arc < Store > ,
@@ -396,7 +443,7 @@ impl ComputeRuntime {
396443 . map_err ( |err| ComputeError :: Message ( err. to_string ( ) ) ) ?;
397444 let driver: SharedComputeDriver = Arc :: new ( PodmanDriverService :: new ( driver) ) ;
398445 Self :: from_driver (
399- ComputeDriverKind :: Podman ,
446+ Some ( ComputeDriverKind :: Podman ) ,
400447 driver,
401448 None ,
402449 None ,
@@ -1371,6 +1418,38 @@ impl ComputeRuntime {
13711418 }
13721419}
13731420
1421+ /// Connect to an out-of-tree compute driver that is already listening on
1422+ /// `socket_path` and return a tonic `Channel` speaking `compute_driver.proto`.
1423+ ///
1424+ /// The gateway does not spawn or own the driver process — the operator is
1425+ /// responsible for placing the driver alongside the gateway and granting the
1426+ /// gateway uid read/write on the socket. The host portion of the URL is
1427+ /// ignored because the connector resolves to the UDS rather than DNS.
1428+ #[ cfg( unix) ]
1429+ pub async fn connect_external_compute_driver ( socket_path : & Path ) -> Result < Channel , ComputeError > {
1430+ let socket_path: PathBuf = socket_path. to_path_buf ( ) ;
1431+ let display_path = socket_path. clone ( ) ;
1432+ Endpoint :: from_static ( "http://[::]:50051" )
1433+ . connect_with_connector ( service_fn ( move |_: tonic:: transport:: Uri | {
1434+ let socket_path = socket_path. clone ( ) ;
1435+ async move { UnixStream :: connect ( socket_path) . await . map ( TokioIo :: new) }
1436+ } ) )
1437+ . await
1438+ . map_err ( |e| {
1439+ ComputeError :: Message ( format ! (
1440+ "failed to connect to external compute driver socket '{}': {e}" ,
1441+ display_path. display( )
1442+ ) )
1443+ } )
1444+ }
1445+
1446+ #[ cfg( not( unix) ) ]
1447+ pub async fn connect_external_compute_driver ( _socket_path : & Path ) -> Result < Channel , ComputeError > {
1448+ Err ( ComputeError :: Message (
1449+ "the external compute driver requires unix domain socket support" . to_string ( ) ,
1450+ ) )
1451+ }
1452+
13741453fn driver_sandbox_from_public (
13751454 sandbox : & Sandbox ,
13761455 driver_kind : Option < ComputeDriverKind > ,
0 commit comments