Skip to content

Commit 426ad69

Browse files
committed
feat(server): connect external compute driver via acquired UDS endpoint
Splits ComputeRuntime construction so that the gateway can dispatch to an out-of-tree compute driver process listening on a Unix domain socket the operator already owns, without adding a fifth `ComputeDriverKind` variant. * `ComputeRuntime::from_driver` now takes `Option<ComputeDriverKind>`; the four in-tree constructors wrap their kind in `Some(...)`. Out-of- tree drivers pass `None` so callers keyed off the enum skip the in-tree match. * `connect_external_compute_driver` produces a tonic `Channel` over a pre-existing UDS path, mirroring the vm driver's connector. A `#[cfg(not(unix))]` stub returns the same error the vm path uses. * `ComputeRuntime::new_remote_external` consumes the channel via the existing `RemoteComputeDriver` proxy and skips both shutdown cleanup and managed-process supervision (the operator owns the lifecycle). * `from_driver` logs the `driver_name` advertised by `GetCapabilities` whenever `driver_kind` is `None`, so operators can confirm the gateway connected to the driver they expect. * `build_compute_runtime` short-circuits to the external path when `Config.external_compute_driver_socket` is set, before consulting `--drivers` / auto-detect. The four in-tree backends (Kubernetes, Vm, Docker, Podman) keep their existing dispatch arms unchanged. Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>
1 parent ae6223a commit 426ad69

2 files changed

Lines changed: 111 additions & 12 deletions

File tree

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

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,20 @@ use openshell_driver_kubernetes::{
3636
use openshell_driver_podman::{
3737
ComputeDriverService as PodmanDriverService, PodmanComputeConfig, PodmanComputeDriver,
3838
};
39+
use hyper_util::rt::TokioIo;
3940
use prost::Message;
4041
use std::fmt;
4142
use std::net::SocketAddr;
43+
use std::path::{Path, PathBuf};
4244
use std::pin::Pin;
4345
use std::sync::Arc;
4446
use std::time::Duration;
47+
#[cfg(unix)]
48+
use tokio::net::UnixStream;
4549
use tokio::sync::{Mutex, watch};
46-
use tonic::transport::Channel;
50+
use tonic::transport::{Channel, Endpoint};
4751
use tonic::{Code, Request, Status};
52+
use tower::service_fn;
4853
use tracing::{debug, info, warn};
4954

5055
type DriverWatchStream = Pin<Box<dyn Stream<Item = Result<WatchSandboxesEvent, Status>> + Send>>;
@@ -102,11 +107,11 @@ pub use openshell_core::ComputeDriverError as ComputeError;
102107
#[derive(Debug)]
103108
pub struct ManagedDriverProcess {
104109
child: std::sync::Mutex<Option<tokio::process::Child>>,
105-
socket_path: std::path::PathBuf,
110+
socket_path: PathBuf,
106111
}
107112

108113
impl 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 {
245250
impl 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+
13741453
fn driver_sandbox_from_public(
13751454
sandbox: &Sandbox,
13761455
driver_kind: Option<ComputeDriverKind>,

crates/openshell-server/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,26 @@ async fn build_compute_runtime(
709709
tracing_log_bus: TracingLogBus,
710710
supervisor_sessions: Arc<supervisor_session::SupervisorSessionRegistry>,
711711
) -> Result<ComputeRuntime> {
712+
if let Some(socket_path) = config.external_compute_driver_socket.as_deref() {
713+
info!(
714+
socket = %socket_path.display(),
715+
"Using external compute driver"
716+
);
717+
let channel = compute::connect_external_compute_driver(socket_path)
718+
.await
719+
.map_err(|e| Error::execution(format!("failed to create compute runtime: {e}")))?;
720+
return ComputeRuntime::new_remote_external(
721+
channel,
722+
store,
723+
sandbox_index,
724+
sandbox_watch_bus,
725+
tracing_log_bus,
726+
supervisor_sessions,
727+
)
728+
.await
729+
.map_err(|e| Error::execution(format!("failed to create compute runtime: {e}")));
730+
}
731+
712732
let driver = configured_compute_driver(config)?;
713733
info!(driver = %driver, "Using compute driver");
714734
warn_if_kubernetes_sandbox_jwt_expiry_disabled(config, driver);

0 commit comments

Comments
 (0)