From 4a5a2f79eb640efd8d115e19783ffa152a85040b Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 18 Sep 2026 00:08:33 -0700 Subject: [PATCH 1/2] refactor(vm): move managed launch into driver Signed-off-by: Drew Newberry --- Cargo.lock | 11 +- architecture/compute-runtimes.md | 15 +- crates/openshell-driver-vm/Cargo.toml | 7 +- crates/openshell-driver-vm/README.md | 5 +- crates/openshell-driver-vm/src/lib.rs | 4 + .../src/managed.rs} | 168 ++++-------------- crates/openshell-gateway/Cargo.toml | 19 +- crates/openshell-gateway/src/lib.rs | 20 +-- crates/openshell-server/src/compute/mod.rs | 121 ++++++++++++- crates/openshell-server/src/lib.rs | 1 + 10 files changed, 189 insertions(+), 182 deletions(-) rename crates/{openshell-gateway/src/vm.rs => openshell-driver-vm/src/managed.rs} (88%) diff --git a/Cargo.lock b/Cargo.lock index 109e6df8d5..c981e9a402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4313,6 +4313,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "toml", "tonic", "tower-http", "tracing", @@ -4344,25 +4345,17 @@ name = "openshell-gateway" version = "0.0.0" dependencies = [ "async-trait", - "hyper-util", "miette", - "nix 0.29.0", "openshell-core", "openshell-driver-docker", "openshell-driver-kubernetes", "openshell-driver-mxc", "openshell-driver-podman", + "openshell-driver-vm", "openshell-otel", - "openshell-policy", "openshell-server", - "rustix 1.1.4", - "serde", "tempfile", "tokio", - "toml", - "tonic", - "tower", - "tracing", ] [[package]] diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index 0077c86689..6edb57a15e 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -153,6 +153,13 @@ compose that gateway with Docker, Podman, Kubernetes, and VM driver executables over the public UDS gRPC contract so an in-tree driver cannot silently depend on a server-only API. +Driver crates own their configuration defaults and backend-specific startup +contract. The standalone VM driver exposes a lightweight `managed` feature for +its configuration and subprocess arguments; this does not link libkrun or the +VM runtime into the gateway. The gateway composition crate only adapts that +launcher to `ComputeDriverFactory`. The server owns the generic managed-child +readiness probe, UDS connection, supervision, and socket cleanup. + ## Stop and Start Lifecycle The gateway persists lifecycle intent before mutating compute: @@ -328,10 +335,10 @@ can request a specific number of GPUs or the driver-specific default behaviour. For all in-tree drivers, this is equivalent to selecting a single GPU. VM runtime state paths are derived only from driver-validated sandbox IDs -matching `[A-Za-z0-9._-]{1,128}`. The gateway-owned VM driver socket uses a -private `run/` directory plus Unix peer UID/PID checks. Standalone -unauthenticated TCP mode is disabled unless explicitly enabled for local -development. +matching `[A-Za-z0-9._-]{1,128}`. The gateway-managed VM driver socket uses a +driver-configured private `run/` directory plus Unix peer UID/PID checks. +Standalone unauthenticated TCP mode is disabled unless explicitly enabled for +local development. Runtime-specific implementation notes belong in the driver crate README: diff --git a/crates/openshell-driver-vm/Cargo.toml b/crates/openshell-driver-vm/Cargo.toml index 9c2ef8c81d..8e6f6a5f8a 100644 --- a/crates/openshell-driver-vm/Cargo.toml +++ b/crates/openshell-driver-vm/Cargo.toml @@ -65,8 +65,12 @@ zstd = { version = "0.13", optional = true } [features] default = ["compute-driver", "telemetry"] +## Expose VM-specific managed launch configuration without linking the VM +## runtime implementation into the gateway process. +managed = ["dep:openshell-policy", "dep:rustix"] ## Build the standalone compute driver and its host runtime implementation. compute-driver = [ + "managed", "dep:base64", "dep:bollard", "dep:clap", @@ -78,7 +82,6 @@ compute-driver = [ "dep:nix", "dep:oci-client", "dep:openshell-otel", - "dep:openshell-policy", "dep:openshell-driver-podman", "dep:openshell-vfio", "dep:opentelemetry", @@ -87,7 +90,6 @@ compute-driver = [ "dep:prost", "dep:prost-types", "dep:rand", - "dep:rustix", "dep:sha2", "dep:tar", "dep:tokio-stream", @@ -117,6 +119,7 @@ defaults-without-telemetry = ["compute-driver"] openshell-otel-test-support = { path = "../openshell-otel-test-support" } temp-env = "0.3" tempfile = "3" +toml = { workspace = true } opentelemetry_sdk = { workspace = true, features = ["testing"] } # smol-rs/polling drives the BSD/macOS parent-death detection in diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index 2cce3b1c04..7a6e943360 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -11,7 +11,7 @@ The driver embeds libkrun, libkrunfw, the guest OCI unpacker, the portable guest ```mermaid flowchart LR subgraph host["Host"] - gateway["openshell-gateway
(vm::spawn)"] + gateway["openshell-gateway
(VM factory adapter)"] driver["openshell-driver-vm
libkrun"] supervisor["openshell-supervisor
host policy supervisor"] gateway <-->|"gRPC over UDS
compute-driver.sock"| driver @@ -305,5 +305,6 @@ so driver choice remains automatic unless the user explicitly overrides it. ## TODOs -- The gateway still configures the driver via CLI args; this will move to a gRPC bootstrap call so the driver interface is uniform across backends. See the `TODO(driver-abstraction)` note in `crates/openshell-gateway/src/vm.rs`. +- Managed launch still configures the driver via CLI args; a future gRPC + bootstrap call can make configuration uniform across standalone backends. - macOS local builds are codesigned by `tasks/scripts/gateway-vm.sh`; the generated Homebrew formula signs the release tarball driver for local installs. diff --git a/crates/openshell-driver-vm/src/lib.rs b/crates/openshell-driver-vm/src/lib.rs index 8e3f41a15d..1e2cd077ec 100644 --- a/crates/openshell-driver-vm/src/lib.rs +++ b/crates/openshell-driver-vm/src/lib.rs @@ -23,6 +23,8 @@ pub mod gpu; mod isolation; #[cfg(feature = "compute-driver")] pub mod lifecycle; +#[cfg(feature = "managed")] +mod managed; #[cfg(feature = "compute-driver")] pub mod otel_tracing; #[cfg(feature = "compute-driver")] @@ -40,6 +42,8 @@ pub use lifecycle::{ LaunchPlan, LifecycleError, LifecycleExtension, LifecycleExtensionRegistry, LifecycleResult, RestoreContext, }; +#[cfg(feature = "managed")] +pub use managed::{ManagedVmDriverProcess, VmComputeConfig, spawn_managed_vm_driver}; #[cfg(feature = "compute-driver")] pub use runtime::{ VM_RUNTIME_DIR_ENV, VmBackend, VmLaunchConfig, VsockPortMap, configured_runtime_dir, run_vm, diff --git a/crates/openshell-gateway/src/vm.rs b/crates/openshell-driver-vm/src/managed.rs similarity index 88% rename from crates/openshell-gateway/src/vm.rs rename to crates/openshell-driver-vm/src/managed.rs index 413d5a579b..41acb76329 100644 --- a/crates/openshell-gateway/src/vm.rs +++ b/crates/openshell-driver-vm/src/managed.rs @@ -1,59 +1,31 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -//! VM compute driver plumbing. +//! Managed VM compute driver launch support. //! -//! This module owns everything needed to hand the gateway a `Channel` speaking -//! the `openshell.compute.v1.ComputeDriver` RPC surface against an -//! `openshell-driver-vm` subprocess over a Unix domain socket: +//! This module owns the VM-specific configuration and process launch contract +//! for an `openshell-driver-vm` subprocess: //! -//! - [`VmComputeConfig`]: gateway-local configuration (state dir, driver binary, +//! - [`VmComputeConfig`]: launch configuration (state dir, driver binary, //! VM shape, guest TLS material). -//! - [`spawn`]: spawn the driver subprocess, wait for its UDS to be ready, -//! and return a live gRPC channel plus a [`ManagedDriverProcess`] handle -//! that will reap the subprocess and clean up the socket on drop. +//! - [`spawn_managed_vm_driver`]: spawn the driver subprocess and return its +//! child handle and Unix domain socket path. //! - Helpers to resolve the driver binary, compute the socket path, and -//! validate guest TLS material when the gateway runs an `https://` control -//! plane. +//! validate guest TLS material for an `https://` control-plane endpoint. //! -//! The VM-driver fields deliberately live here rather than in -//! [`openshell_core::Config`] so the shared core stays free of driver-specific -//! plumbing. -//! -//! Process launch remains deliberately VM-specific at this binary composition -//! boundary: it translates gateway configuration into the standalone driver's -//! argv and then connects through the same public compute-driver RPC interface -//! used by operator-managed external drivers. +//! Socket readiness, RPC connection, process supervision, and cleanup remain +//! generic server concerns. -#[cfg(unix)] -use hyper_util::rt::TokioIo; -#[cfg(unix)] -use openshell_core::proto::compute::v1::{ - GetCapabilitiesRequest, compute_driver_client::ComputeDriverClient, -}; use openshell_core::{Error, Result, UpstreamProxyConfig}; #[cfg(unix)] -use openshell_otel::TraceContextInterceptor; -use openshell_server::AcquiredRemoteDriverEndpoint; -#[cfg(unix)] -use openshell_server::ManagedDriverProcess; -use openshell_server::config_file::OtlpConfig; -#[cfg(unix)] use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; #[cfg(unix)] use std::path::Path; use std::path::PathBuf; #[cfg(unix)] -use std::{io::ErrorKind, process::Stdio, sync::Arc, time::Duration}; -#[cfg(unix)] -use tokio::net::UnixStream; +use std::{io::ErrorKind, process::Stdio}; #[cfg(unix)] use tokio::process::Command; -use tonic::transport::Channel; -#[cfg(unix)] -use tonic::transport::Endpoint; -#[cfg(unix)] -use tower::service_fn; const DRIVER_BIN_NAME: &str = "openshell-driver-vm"; const COMPUTE_DRIVER_SOCKET_RUN_DIR: &str = "run"; @@ -533,16 +505,28 @@ pub fn compute_driver_guest_tls_paths( Ok(Some(VmGuestTlsPaths { ca, cert, key })) } -/// Launch the VM compute-driver subprocess, wait for its UDS to come up, -/// and return a gRPC `Channel` connected to it plus a process handle that -/// kills the subprocess and removes the socket on drop. +/// A launched VM compute-driver process and the socket it will listen on. +pub struct ManagedVmDriverProcess { + child: tokio::process::Child, + socket_path: PathBuf, +} + +impl ManagedVmDriverProcess { + /// Consume the launch result into the generic server-owned process parts. + #[must_use] + pub fn into_parts(self) -> (tokio::process::Child, PathBuf) { + (self.child, self.socket_path) + } +} + +/// Launch the VM compute-driver subprocess. #[cfg(unix)] -pub async fn spawn( +pub fn spawn_managed_vm_driver( gateway_log_level: &str, gateway_name: &str, vm_config: &VmComputeConfig, - otlp_config: Option<&OtlpConfig>, -) -> Result { + otlp_endpoint: Option<&str>, +) -> Result { vm_config.validate_configuration()?; let driver_bin = resolve_compute_driver_bin(vm_config)?; let socket_path = compute_driver_socket_path(vm_config); @@ -559,7 +543,7 @@ pub async fn spawn( .arg("--expected-peer-pid") .arg(std::process::id().to_string()); command.arg("--log-level").arg(gateway_log_level); - append_otlp_args(&mut command, otlp_config, gateway_name); + append_otlp_args(&mut command, otlp_endpoint, gateway_name); command.arg("--grpc-endpoint").arg(&vm_config.grpc_endpoint); command.arg("--state-dir").arg(&vm_config.state_dir); if !vm_config.default_image.trim().is_empty() { @@ -587,17 +571,13 @@ pub async fn spawn( } append_vm_proxy_and_spiffe_args(&mut command, vm_config); - let mut child = command.spawn().map_err(|e| { + let child = command.spawn().map_err(|e| { Error::execution(format!( "failed to launch vm compute driver '{}': {e}", driver_bin.display() )) })?; - let channel = wait_for_compute_driver(&socket_path, &mut child).await?; - let process = Arc::new(ManagedDriverProcess::new(child, socket_path)); - Ok(AcquiredRemoteDriverEndpoint::managed( - "vm", channel, process, - )) + Ok(ManagedVmDriverProcess { child, socket_path }) } fn validate_vm_sandbox_identity(config: &VmComputeConfig) -> Result<()> { @@ -670,93 +650,25 @@ fn append_vm_proxy_and_spiffe_args(command: &mut Command, config: &VmComputeConf } } -fn append_otlp_args(command: &mut Command, otlp_config: Option<&OtlpConfig>, gateway_name: &str) { - if let Some(config) = otlp_config { - command.arg("--otlp-endpoint").arg(&config.endpoint); +fn append_otlp_args(command: &mut Command, otlp_endpoint: Option<&str>, gateway_name: &str) { + if let Some(endpoint) = otlp_endpoint { + command.arg("--otlp-endpoint").arg(endpoint); command.arg("--gateway-name").arg(gateway_name); } } #[cfg(not(unix))] -pub async fn spawn( +pub fn spawn_managed_vm_driver( _gateway_log_level: &str, _gateway_name: &str, _vm_config: &VmComputeConfig, - _otlp_config: Option<&OtlpConfig>, -) -> Result { + _otlp_endpoint: Option<&str>, +) -> Result { Err(Error::config( "the vm compute driver requires unix domain socket support", )) } -#[cfg(unix)] -#[tracing::instrument( - name = "driver.wait_for_ready", - skip_all, - fields( - otel.name = "driver.wait_for_ready", - otel.status_code = tracing::field::Empty, - driver.name = "vm", - ) -)] -async fn wait_for_compute_driver( - socket_path: &Path, - child: &mut tokio::process::Child, -) -> Result { - let mut last_error: Option = None; - for _ in 0..100 { - let try_wait_result = child.try_wait().map_err(|e| { - Error::execution(format!("failed to poll vm compute driver process: {e}")) - })?; - if let Some(status) = try_wait_result { - return Err(Error::execution(format!( - "vm compute driver exited before becoming ready with status {status}" - ))); - } - - match connect_compute_driver(socket_path).await { - Ok(channel) => { - let mut client = - ComputeDriverClient::with_interceptor(channel.clone(), TraceContextInterceptor); - match client - .get_capabilities(tonic::Request::new(GetCapabilitiesRequest {})) - .await - { - Ok(_) => return Ok(channel), - Err(status) => last_error = Some(status.to_string()), - } - } - Err(err) => last_error = Some(err.to_string()), - } - - tokio::time::sleep(Duration::from_millis(100)).await; - } - - Err(Error::execution(format!( - "timed out waiting for vm compute driver socket '{}': {}", - socket_path.display(), - last_error.unwrap_or_else(|| "unknown error".to_string()) - ))) -} - -#[cfg(unix)] -async fn connect_compute_driver(socket_path: &Path) -> Result { - let socket_path = socket_path.to_path_buf(); - let display_path = socket_path.clone(); - Endpoint::from_static("http://[::]:50051") - .connect_with_connector(service_fn(move |_: tonic::transport::Uri| { - let socket_path = socket_path.clone(); - async move { UnixStream::connect(socket_path).await.map(TokioIo::new) } - })) - .await - .map_err(|e| { - Error::execution(format!( - "failed to connect to vm compute driver socket '{}': {e}", - display_path.display() - )) - }) -} - #[cfg(all(test, unix))] mod tests { use super::{ @@ -767,7 +679,6 @@ mod tests { validate_vm_sandbox_identity, }; use openshell_core::UpstreamProxyConfig; - use openshell_server::config_file::OtlpConfig; use std::os::unix::fs::PermissionsExt; use std::os::unix::net::UnixListener as StdUnixListener; use std::path::PathBuf; @@ -786,10 +697,7 @@ mod tests { let mut command = tokio::process::Command::new("openshell-driver-vm"); append_otlp_args( &mut command, - Some(&OtlpConfig { - endpoint: "http://collector.internal:4317".to_string(), - service_name: Some("custom-gateway".to_string()), - }), + Some("http://collector.internal:4317"), "production-us-west", ); diff --git a/crates/openshell-gateway/Cargo.toml b/crates/openshell-gateway/Cargo.toml index 32dcaeb24c..fa6f5d9be3 100644 --- a/crates/openshell-gateway/Cargo.toml +++ b/crates/openshell-gateway/Cargo.toml @@ -26,14 +26,7 @@ tokio = { workspace = true } openshell-driver-docker = { path = "../openshell-driver-docker", optional = true } openshell-driver-kubernetes = { path = "../openshell-driver-kubernetes", optional = true } openshell-driver-podman = { path = "../openshell-driver-podman", optional = true } -openshell-policy = { path = "../openshell-policy", optional = true } -hyper-util = { workspace = true, optional = true } -nix = { workspace = true, optional = true } -serde = { workspace = true, optional = true } -rustix = { workspace = true, optional = true } -tonic = { workspace = true, optional = true } -tower = { workspace = true, optional = true } -tracing = { workspace = true, optional = true } +openshell-driver-vm = { path = "../openshell-driver-vm", default-features = false, features = ["managed"], optional = true } [target.'cfg(target_os = "windows")'.dependencies] openshell-driver-mxc = { path = "../openshell-driver-mxc", optional = true } @@ -52,15 +45,8 @@ compute-driver-docker = ["dep:openshell-driver-docker", "dep:openshell-otel"] compute-driver-kubernetes = ["dep:openshell-driver-kubernetes", "dep:openshell-otel"] compute-driver-podman = ["dep:openshell-driver-podman", "dep:openshell-otel"] compute-driver-vm = [ - "dep:openshell-policy", + "dep:openshell-driver-vm", "dep:openshell-otel", - "dep:hyper-util", - "dep:nix", - "dep:serde", - "dep:rustix", - "dep:tonic", - "dep:tower", - "dep:tracing", ] telemetry = ["openshell-core/telemetry", "openshell-server/telemetry"] ## Convenience alias: every default feature except `telemetry`. Build a @@ -79,4 +65,3 @@ workspace = true [dev-dependencies] tempfile = "3" -toml = { workspace = true } diff --git a/crates/openshell-gateway/src/lib.rs b/crates/openshell-gateway/src/lib.rs index b67e93c102..260928dbaa 100644 --- a/crates/openshell-gateway/src/lib.rs +++ b/crates/openshell-gateway/src/lib.rs @@ -16,9 +16,6 @@ compile_error!( build a telemetry-free gateway with `--no-default-features --features defaults-without-telemetry`" ); -#[cfg(all(not(target_os = "windows"), feature = "compute-driver-vm"))] -mod vm; - #[cfg(any( all(target_os = "windows", feature = "compute-driver-mxc"), all( @@ -436,13 +433,16 @@ impl openshell_server::ComputeDriverFactory for VmFactory { &mut config.guest_tls_key, context.guest_tls_paths(), ); - let endpoint = vm::spawn( + let launch = openshell_driver_vm::spawn_managed_vm_driver( context.gateway_log_level(), context.gateway_name(), &config, - context.otlp_config(), - ) - .await?; + context.otlp_config().map(|config| config.endpoint.as_str()), + )?; + let (child, socket_path) = launch.into_parts(); + let endpoint = openshell_server::connect_managed_compute_driver("vm", socket_path, child) + .await + .map_err(|error| openshell_core::Error::execution(error.to_string()))?; Ok(openshell_server::ComputeDriverInstance::ManagedRemote( endpoint, )) @@ -452,10 +452,10 @@ impl openshell_server::ComputeDriverFactory for VmFactory { #[cfg(all(not(target_os = "windows"), feature = "compute-driver-vm"))] fn vm_config( context: openshell_server::ComputeDriverConfigContext<'_>, -) -> openshell_core::Result { - let mut config: vm::VmComputeConfig = context.driver_config()?; +) -> openshell_core::Result { + let mut config: openshell_driver_vm::VmComputeConfig = context.driver_config()?; if config.state_dir.as_os_str().is_empty() { - config.state_dir = vm::VmComputeConfig::default_state_dir(); + config.state_dir = openshell_driver_vm::VmComputeConfig::default_state_dir(); } Ok(config) } diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 9732999c31..d6f53a2a46 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -451,6 +451,28 @@ async fn managed_driver_shutdown_sends_sigterm_before_forcing_exit() { assert_eq!(std::fs::read_to_string(terminated).unwrap(), "terminated"); } +#[cfg(all(test, unix))] +#[tokio::test] +async fn managed_driver_connection_reports_early_process_exit() { + let dir = tempfile::tempdir().unwrap(); + let child = tokio::process::Command::new("sh") + .arg("-c") + .arg("exit 7") + .spawn() + .unwrap(); + + let error = + connect_managed_compute_driver("test", dir.path().join("missing-driver.sock"), child) + .await + .expect_err("an exited driver must fail readiness"); + + assert!( + error + .to_string() + .contains("test compute driver exited before becoming ready") + ); +} + #[derive(Debug)] pub struct AcquiredRemoteDriverEndpoint { pub(crate) name: String, @@ -4237,6 +4259,96 @@ fn is_failed_main_process_result(sandbox: &Sandbox) -> bool { }) } +/// Connect to a compute driver subprocess owned by the gateway. +/// +/// The caller owns driver-specific process construction. The server owns the +/// generic readiness probe, process supervision, and socket cleanup after the +/// child and socket path cross this boundary. +#[cfg(unix)] +#[tracing::instrument( + name = "driver.wait_for_ready", + skip_all, + fields( + otel.name = "driver.wait_for_ready", + otel.status_code = tracing::field::Empty, + driver.name = tracing::field::Empty, + ) +)] +pub async fn connect_managed_compute_driver( + name: impl Into, + socket_path: PathBuf, + mut child: tokio::process::Child, +) -> Result { + let name = name.into(); + tracing::Span::current().record("driver.name", &name); + let mut last_error: Option = None; + + for _ in 0..100 { + let status = child.try_wait().map_err(|error| { + ComputeError::Message(format!( + "failed to poll {name} compute driver process: {error}" + )) + })?; + if let Some(status) = status { + return Err(ComputeError::Message(format!( + "{name} compute driver exited before becoming ready with status {status}" + ))); + } + + match connect_compute_driver_socket(&socket_path).await { + Ok(channel) => { + let mut client = + ComputeDriverClient::with_interceptor(channel.clone(), TraceContextInterceptor); + match client + .get_capabilities(Request::new(GetCapabilitiesRequest {})) + .await + { + Ok(_) => { + let process = Arc::new(ManagedDriverProcess::new(child, socket_path)); + return Ok(AcquiredRemoteDriverEndpoint::managed( + name, channel, process, + )); + } + Err(status) => last_error = Some(status.to_string()), + } + } + Err(error) => last_error = Some(error.to_string()), + } + + tokio::time::sleep(Duration::from_millis(100)).await; + } + + Err(ComputeError::Message(format!( + "timed out waiting for {name} compute driver socket '{}': {}", + socket_path.display(), + last_error.unwrap_or_else(|| "unknown error".to_string()) + ))) +} + +#[cfg(not(unix))] +pub async fn connect_managed_compute_driver( + _name: impl Into, + _socket_path: PathBuf, + _child: tokio::process::Child, +) -> Result { + Err(ComputeError::Message( + "managed compute driver endpoints require unix domain socket support".to_string(), + )) +} + +#[cfg(unix)] +async fn connect_compute_driver_socket( + socket_path: &Path, +) -> Result { + let connector_path = socket_path.to_path_buf(); + Endpoint::from_static("http://[::]:50051") + .connect_with_connector(service_fn(move |_: tonic::transport::Uri| { + let connector_path = connector_path.clone(); + async move { UnixStream::connect(connector_path).await.map(TokioIo::new) } + })) + .await +} + /// Connect to an unmanaged remote compute driver that is already listening on /// `socket_path` and return the acquired endpoint. /// @@ -4252,14 +4364,7 @@ pub async fn connect_remote_compute_driver( let socket_path = socket_path.to_path_buf(); let deadline = tokio::time::Instant::now() + Duration::from_secs(30); let channel = loop { - let connector_path = socket_path.clone(); - match Endpoint::from_static("http://[::]:50051") - .connect_with_connector(service_fn(move |_: tonic::transport::Uri| { - let connector_path = connector_path.clone(); - async move { UnixStream::connect(connector_path).await.map(TokioIo::new) } - })) - .await - { + match connect_compute_driver_socket(&socket_path).await { Ok(channel) => break channel, Err(error) if tokio::time::Instant::now() < deadline => { tracing::debug!( diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 80cc2acd57..69b051e863 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -1106,6 +1106,7 @@ async fn terminate_signal() { pub use compute::{ AcquiredRemoteDriverEndpoint, DriverWatchStream, ManagedDriverProcess, SharedComputeDriver, + connect_managed_compute_driver, }; /// Driver instance returned by a compiled compute-driver factory. From 53899cd0b61123595a78a05a1ceb0eafb185b507 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 18 Sep 2026 00:29:01 -0700 Subject: [PATCH 2/2] refactor(vm): own gateway registration in driver Signed-off-by: Drew Newberry --- .github/workflows/branch-checks.yml | 4 +- Cargo.lock | 2 + Cargo.toml | 2 + README.md | 6 +- architecture/compute-runtimes.md | 7 +- crates/openshell-driver-vm/Cargo.toml | 5 + crates/openshell-driver-vm/README.md | 4 +- crates/openshell-driver-vm/src/gateway.rs | 141 ++++++++++++++++++++ crates/openshell-driver-vm/src/lib.rs | 4 + crates/openshell-gateway/Cargo.toml | 9 +- crates/openshell-gateway/src/lib.rs | 155 +++++----------------- 11 files changed, 198 insertions(+), 141 deletions(-) create mode 100644 crates/openshell-driver-vm/src/gateway.rs diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index 31b590e572..480bf61379 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -208,9 +208,9 @@ jobs: cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-docker cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-kubernetes cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-podman - cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-vm + cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-managed cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-mxc - cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-docker,compute-driver-vm + cargo test -p openshell-gateway --all-targets --no-default-features --features compute-driver-docker,compute-driver-managed - name: Verify the defaults-without-telemetry feature alias tracks the default feature set run: tasks/scripts/verify-defaults-without-telemetry.sh diff --git a/Cargo.lock b/Cargo.lock index c981e9a402..ebd72d51e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4279,6 +4279,7 @@ dependencies = [ name = "openshell-driver-vm" version = "0.0.0" dependencies = [ + "async-trait", "base64", "bollard", "clap", @@ -4297,6 +4298,7 @@ dependencies = [ "openshell-otel-test-support", "openshell-policy", "openshell-sandbox-backend", + "openshell-server", "openshell-vfio", "opentelemetry", "opentelemetry_sdk", diff --git a/Cargo.toml b/Cargo.toml index f57b22c397..5818c8c1c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ license = "Apache-2.0" repository = "https://github.com/NVIDIA/OpenShell" [workspace.dependencies] +openshell-managed-compute-driver = { package = "openshell-driver-vm", path = "crates/openshell-driver-vm", default-features = false } + # Async runtime tokio = { version = "1.43", features = ["full"] } diff --git a/README.md b/README.md index f23ddb63c5..917407ca34 100644 --- a/README.md +++ b/README.md @@ -349,20 +349,20 @@ cargo build --release -p openshell-driver-vm --no-default-features --features de The resulting binaries contain no telemetry endpoint, no telemetry HTTP client, and no emission code. With telemetry compiled out, the gateway emits nothing and reports telemetry disabled to the sandboxes it launches. Cargo has no way to subtract a single default feature, so `defaults-without-telemetry` must be paired with `--no-default-features`; passing it on its own leaves the defaults in place and fails the build rather than producing a binary that still emits. -The gateway also exposes separate Cargo features for its built-in compute drivers: `compute-driver-kubernetes`, `compute-driver-docker`, `compute-driver-podman`, `compute-driver-vm`, and `compute-driver-mxc`. Disable the default feature set, then enable only the drivers and telemetry mode required by the target binary. For example: +The gateway also exposes separate Cargo features for its built-in compute drivers: `compute-driver-kubernetes`, `compute-driver-docker`, `compute-driver-podman`, `compute-driver-managed`, and `compute-driver-mxc`. The driver-owned managed adapter currently registers the standalone VM driver without putting VM-specific code in the gateway crate. Disable the default feature set, then enable only the drivers and telemetry mode required by the target binary. For example: ```shell # Docker only, with telemetry support. cargo build --release -p openshell-gateway --no-default-features --features telemetry,compute-driver-docker # Docker and VM only, with telemetry compiled out. -cargo build --release -p openshell-gateway --no-default-features --features compute-driver-docker,compute-driver-vm +cargo build --release -p openshell-gateway --no-default-features --features compute-driver-docker,compute-driver-managed # Windows MXC only, with telemetry support and bundled Z3. cargo build --release -p openshell-gateway --no-default-features --features telemetry,compute-driver-mxc,bundled-z3 ``` -Regular builds retain their platform driver set through the default `in-tree-compute-drivers` compatibility feature. On Windows, `compute-driver-mxc` selects MXC; the other four features install unsupported-driver stubs. On other platforms, MXC is excluded. +Regular builds retain their platform driver set through the default `in-tree-compute-drivers` compatibility feature. On Windows, `compute-driver-mxc` selects MXC; Docker, Kubernetes, and Podman install unsupported-driver stubs, while the managed standalone adapter is excluded. On other platforms, MXC is excluded. Telemetry events are limited to anonymous operational categories and counts, such as sandbox lifecycle outcomes, provider profile buckets, policy decision counts, and aggregate network activity denial categories. OpenShell telemetry does not collect sandbox names or IDs, hostnames, file paths, binary paths, prompts, credentials, provider names, model names, or user content. diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index 6edb57a15e..feefbddc20 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -156,9 +156,10 @@ on a server-only API. Driver crates own their configuration defaults and backend-specific startup contract. The standalone VM driver exposes a lightweight `managed` feature for its configuration and subprocess arguments; this does not link libkrun or the -VM runtime into the gateway. The gateway composition crate only adapts that -launcher to `ComputeDriverFactory`. The server owns the generic managed-child -readiness probe, UDS connection, supervision, and socket cleanup. +VM runtime into the gateway. Its optional `gateway-integration` feature owns the +`ComputeDriverFactory` adapter and exports an opaque registration; the gateway +only installs that provider. The server owns the generic managed-child readiness +probe, UDS connection, supervision, and socket cleanup. ## Stop and Start Lifecycle diff --git a/crates/openshell-driver-vm/Cargo.toml b/crates/openshell-driver-vm/Cargo.toml index 8e6f6a5f8a..9d70499153 100644 --- a/crates/openshell-driver-vm/Cargo.toml +++ b/crates/openshell-driver-vm/Cargo.toml @@ -31,7 +31,9 @@ openshell-otel = { path = "../openshell-otel", optional = true } openshell-policy = { path = "../openshell-policy", optional = true } openshell-driver-podman = { path = "../openshell-driver-podman", optional = true } openshell-vfio = { path = "../openshell-vfio", optional = true } +openshell-server = { path = "../openshell-server", default-features = false, optional = true } +async-trait = { version = "0.1", optional = true } base64 = { workspace = true, optional = true } bollard = { version = "0.20", features = ["ssh"], optional = true } tokio = { workspace = true } @@ -68,6 +70,9 @@ default = ["compute-driver", "telemetry"] ## Expose VM-specific managed launch configuration without linking the VM ## runtime implementation into the gateway process. managed = ["dep:openshell-policy", "dep:rustix"] +## Adapt the standalone driver to the standard gateway registry. The adapter +## remains driver-owned and does not pull the VM runtime into the gateway. +gateway-integration = ["managed", "dep:async-trait", "dep:openshell-server"] ## Build the standalone compute driver and its host runtime implementation. compute-driver = [ "managed", diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index 7a6e943360..752f8e76e6 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -11,8 +11,8 @@ The driver embeds libkrun, libkrunfw, the guest OCI unpacker, the portable guest ```mermaid flowchart LR subgraph host["Host"] - gateway["openshell-gateway
(VM factory adapter)"] - driver["openshell-driver-vm
libkrun"] + gateway["openshell-gateway
(generic registry consumer)"] + driver["openshell-driver-vm
driver-owned registry adapter + libkrun"] supervisor["openshell-supervisor
host policy supervisor"] gateway <-->|"gRPC over UDS
compute-driver.sock"| driver supervisor <-->|"authenticated gRPC
policy + relay"| gateway diff --git a/crates/openshell-driver-vm/src/gateway.rs b/crates/openshell-driver-vm/src/gateway.rs new file mode 100644 index 0000000000..1d89c6b195 --- /dev/null +++ b/crates/openshell-driver-vm/src/gateway.rs @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Optional adapter from the standalone VM driver to the gateway registry. + +use crate::{VmComputeConfig, spawn_managed_vm_driver}; +use openshell_core::telemetry::TelemetryComputeDriver; +use openshell_core::{Error, Result}; +use openshell_server::{ + ComputeDriverBuildContext, ComputeDriverConfigContext, ComputeDriverFactory, + ComputeDriverInstance, ComputeDriverRegistration, connect_managed_compute_driver, +}; +use std::path::{Path, PathBuf}; + +const DRIVER_NAME: &str = "vm"; + +/// Build the VM driver's self-contained gateway registration. +pub fn gateway_registration() -> Result { + ComputeDriverRegistration::new(DRIVER_NAME, u16::MAX, None, VmFactory).map(|registration| { + registration + .with_telemetry_category(TelemetryComputeDriver::anonymous_category(DRIVER_NAME)) + .with_local_singleplayer() + }) +} + +#[derive(Clone, Copy)] +struct VmFactory; + +#[async_trait::async_trait] +impl ComputeDriverFactory for VmFactory { + fn supports_config_preflight(&self) -> bool { + true + } + + fn validate_config(&self, context: ComputeDriverConfigContext<'_>) -> Result<()> { + let mut config = vm_config(context)?; + apply_default_grpc_endpoint( + &mut config, + context.gateway_tls_enabled(), + context.gateway_port(), + ); + config.validate_configuration() + } + + async fn build(&self, context: ComputeDriverBuildContext<'_>) -> Result { + let mut config = vm_config(context.config_context())?; + require_guest_tls(&context)?; + if !context.gateway_tls_enabled() || context.guest_tls_paths().is_some() { + apply_default_grpc_endpoint( + &mut config, + context.gateway_tls_enabled(), + context.gateway_port(), + ); + } + apply_guest_tls( + &mut config.guest_tls_ca, + &mut config.guest_tls_cert, + &mut config.guest_tls_key, + context.guest_tls_paths(), + ); + let launch = spawn_managed_vm_driver( + context.gateway_log_level(), + context.gateway_name(), + &config, + context.otlp_config().map(|config| config.endpoint.as_str()), + )?; + let (child, socket_path) = launch.into_parts(); + let endpoint = connect_managed_compute_driver(DRIVER_NAME, socket_path, child) + .await + .map_err(|error| Error::execution(error.to_string()))?; + Ok(ComputeDriverInstance::ManagedRemote(endpoint)) + } +} + +fn vm_config(context: ComputeDriverConfigContext<'_>) -> Result { + let mut config: VmComputeConfig = context.driver_config()?; + if config.state_dir.as_os_str().is_empty() { + config.state_dir = VmComputeConfig::default_state_dir(); + } + Ok(config) +} + +fn apply_default_grpc_endpoint(config: &mut VmComputeConfig, tls_enabled: bool, port: u16) { + if config.grpc_endpoint.trim().is_empty() { + let scheme = if tls_enabled { "https" } else { "http" }; + config.grpc_endpoint = format!("{scheme}://127.0.0.1:{port}"); + } +} + +fn require_guest_tls(context: &ComputeDriverBuildContext<'_>) -> Result<()> { + if context.gateway_tls_enabled() && context.guest_tls_paths().is_none() { + return Err(Error::config(format!( + "gateway TLS requires guest_tls_ca, guest_tls_cert, and guest_tls_key in [openshell.gateway] when using the {DRIVER_NAME} compute driver" + ))); + } + Ok(()) +} + +fn apply_guest_tls( + ca: &mut Option, + cert: &mut Option, + key: &mut Option, + defaults: Option<(&Path, &Path, &Path)>, +) { + if ca.is_none() + && cert.is_none() + && key.is_none() + && let Some((default_ca, default_cert, default_key)) = defaults + { + *ca = Some(default_ca.to_owned()); + *cert = Some(default_cert.to_owned()); + *key = Some(default_key.to_owned()); + } +} + +#[cfg(test)] +mod tests { + use super::apply_guest_tls; + use std::path::{Path, PathBuf}; + + #[test] + fn package_managed_guest_bundle_is_injected_when_driver_paths_are_absent() { + let mut ca = None; + let mut cert = None; + let mut key = None; + apply_guest_tls( + &mut ca, + &mut cert, + &mut key, + Some(( + Path::new("ca.pem"), + Path::new("client.pem"), + Path::new("client-key.pem"), + )), + ); + + assert_eq!(ca, Some(PathBuf::from("ca.pem"))); + assert_eq!(cert, Some(PathBuf::from("client.pem"))); + assert_eq!(key, Some(PathBuf::from("client-key.pem"))); + } +} diff --git a/crates/openshell-driver-vm/src/lib.rs b/crates/openshell-driver-vm/src/lib.rs index 1e2cd077ec..f97ddea7c5 100644 --- a/crates/openshell-driver-vm/src/lib.rs +++ b/crates/openshell-driver-vm/src/lib.rs @@ -17,6 +17,8 @@ pub mod driver; mod embedded_runtime; #[cfg(feature = "compute-driver")] mod ffi; +#[cfg(all(not(target_os = "windows"), feature = "gateway-integration"))] +mod gateway; #[cfg(feature = "compute-driver")] pub mod gpu; #[cfg(feature = "compute-driver")] @@ -36,6 +38,8 @@ mod runtime; #[cfg(feature = "compute-driver")] pub use driver::{VmDriver, VmDriverConfig}; +#[cfg(all(not(target_os = "windows"), feature = "gateway-integration"))] +pub use gateway::gateway_registration; #[cfg(feature = "compute-driver")] pub use lifecycle::{ BackendFeature, ExtensionCapabilities, ExtensionDescriptor, GuestInitDropin, LaunchAbortReason, diff --git a/crates/openshell-gateway/Cargo.toml b/crates/openshell-gateway/Cargo.toml index fa6f5d9be3..9339d3556d 100644 --- a/crates/openshell-gateway/Cargo.toml +++ b/crates/openshell-gateway/Cargo.toml @@ -23,10 +23,10 @@ miette = { workspace = true } tokio = { workspace = true } [target.'cfg(not(target_os = "windows"))'.dependencies] +openshell-managed-compute-driver = { workspace = true, features = ["gateway-integration"], optional = true } openshell-driver-docker = { path = "../openshell-driver-docker", optional = true } openshell-driver-kubernetes = { path = "../openshell-driver-kubernetes", optional = true } openshell-driver-podman = { path = "../openshell-driver-podman", optional = true } -openshell-driver-vm = { path = "../openshell-driver-vm", default-features = false, features = ["managed"], optional = true } [target.'cfg(target_os = "windows")'.dependencies] openshell-driver-mxc = { path = "../openshell-driver-mxc", optional = true } @@ -36,18 +36,15 @@ default = ["telemetry", "in-tree-compute-drivers"] in-tree-compute-drivers = [ "compute-driver-docker", "compute-driver-kubernetes", + "compute-driver-managed", "compute-driver-podman", - "compute-driver-vm", "compute-driver-mxc", ] compute-driver-mxc = ["dep:openshell-driver-mxc"] compute-driver-docker = ["dep:openshell-driver-docker", "dep:openshell-otel"] compute-driver-kubernetes = ["dep:openshell-driver-kubernetes", "dep:openshell-otel"] +compute-driver-managed = ["dep:openshell-managed-compute-driver"] compute-driver-podman = ["dep:openshell-driver-podman", "dep:openshell-otel"] -compute-driver-vm = [ - "dep:openshell-driver-vm", - "dep:openshell-otel", -] telemetry = ["openshell-core/telemetry", "openshell-server/telemetry"] ## Convenience alias: every default feature except `telemetry`. Build a ## telemetry-free gateway with diff --git a/crates/openshell-gateway/src/lib.rs b/crates/openshell-gateway/src/lib.rs index 260928dbaa..a2546b6ef1 100644 --- a/crates/openshell-gateway/src/lib.rs +++ b/crates/openshell-gateway/src/lib.rs @@ -23,8 +23,7 @@ compile_error!( any( feature = "compute-driver-docker", feature = "compute-driver-kubernetes", - feature = "compute-driver-podman", - feature = "compute-driver-vm" + feature = "compute-driver-podman" ) ) ))] @@ -33,8 +32,7 @@ use openshell_core::telemetry::TelemetryComputeDriver; target_os = "windows", feature = "compute-driver-docker", feature = "compute-driver-kubernetes", - feature = "compute-driver-podman", - feature = "compute-driver-vm" + feature = "compute-driver-podman" ))] use openshell_server::ComputeDriverRegistration; use openshell_server::ComputeDriverRegistry; @@ -49,11 +47,17 @@ pub fn install_default_compute_drivers() -> ComputeDriverRegistry { any( feature = "compute-driver-docker", feature = "compute-driver-kubernetes", - feature = "compute-driver-podman", - feature = "compute-driver-vm" + feature = "compute-driver-podman" ) ))] install_in_tree_compute_drivers(&mut registry); + #[cfg(all(not(target_os = "windows"), feature = "compute-driver-managed"))] + registry + .install( + openshell_managed_compute_driver::gateway_registration() + .expect("managed driver name is valid"), + ) + .expect("first-party driver names are unique"); #[cfg(all(target_os = "windows", feature = "compute-driver-mxc"))] install_mxc_compute_driver(&mut registry); #[cfg(target_os = "windows")] @@ -81,8 +85,6 @@ fn install_unsupported_windows_compute_drivers(registry: &mut ComputeDriverRegis "kubernetes", #[cfg(feature = "compute-driver-podman")] "podman", - #[cfg(feature = "compute-driver-vm")] - "vm", ]; for &name in names { let registration = ComputeDriverRegistration::new( @@ -168,8 +170,7 @@ impl openshell_server::ComputeDriverFactory for MxcFactory { any( feature = "compute-driver-docker", feature = "compute-driver-kubernetes", - feature = "compute-driver-podman", - feature = "compute-driver-vm" + feature = "compute-driver-podman" ) ))] fn install_in_tree_compute_drivers(registry: &mut ComputeDriverRegistry) { @@ -213,12 +214,6 @@ fn install_in_tree_compute_drivers(registry: &mut ComputeDriverRegistry) { .with_local_singleplayer() .with_in_process_tracing(openshell_driver_docker::otel_tracing::TRACING) }), - #[cfg(feature = "compute-driver-vm")] - ComputeDriverRegistration::new("vm", u16::MAX, None, VmFactory).map(|registration| { - registration - .with_telemetry_category(TelemetryComputeDriver::anonymous_category("vm")) - .with_local_singleplayer() - }), ] { registry .install(registration.expect("first-party driver name is valid")) @@ -384,89 +379,9 @@ fn podman_config( Ok(config) } -#[cfg(all(not(target_os = "windows"), feature = "compute-driver-vm"))] -#[derive(Clone, Copy)] -struct VmFactory; - -#[cfg(all(not(target_os = "windows"), feature = "compute-driver-vm"))] -#[async_trait::async_trait] -impl openshell_server::ComputeDriverFactory for VmFactory { - fn supports_config_preflight(&self) -> bool { - true - } - - fn validate_config( - &self, - context: openshell_server::ComputeDriverConfigContext<'_>, - ) -> openshell_core::Result<()> { - let mut config = vm_config(context)?; - if config.grpc_endpoint.trim().is_empty() { - let scheme = if context.gateway_tls_enabled() { - "https" - } else { - "http" - }; - config.grpc_endpoint = format!("{scheme}://127.0.0.1:{}", context.gateway_port()); - } - config.validate_configuration() - } - - async fn build( - &self, - context: openshell_server::ComputeDriverBuildContext<'_>, - ) -> openshell_core::Result { - let mut config = vm_config(context.config_context())?; - require_guest_tls_for_local_driver(&context, "vm")?; - if config.grpc_endpoint.trim().is_empty() - && (!context.gateway_tls_enabled() || context.guest_tls_paths().is_some()) - { - let scheme = if context.gateway_tls_enabled() { - "https" - } else { - "http" - }; - config.grpc_endpoint = format!("{scheme}://127.0.0.1:{}", context.gateway_port()); - } - apply_guest_tls( - &mut config.guest_tls_ca, - &mut config.guest_tls_cert, - &mut config.guest_tls_key, - context.guest_tls_paths(), - ); - let launch = openshell_driver_vm::spawn_managed_vm_driver( - context.gateway_log_level(), - context.gateway_name(), - &config, - context.otlp_config().map(|config| config.endpoint.as_str()), - )?; - let (child, socket_path) = launch.into_parts(); - let endpoint = openshell_server::connect_managed_compute_driver("vm", socket_path, child) - .await - .map_err(|error| openshell_core::Error::execution(error.to_string()))?; - Ok(openshell_server::ComputeDriverInstance::ManagedRemote( - endpoint, - )) - } -} - -#[cfg(all(not(target_os = "windows"), feature = "compute-driver-vm"))] -fn vm_config( - context: openshell_server::ComputeDriverConfigContext<'_>, -) -> openshell_core::Result { - let mut config: openshell_driver_vm::VmComputeConfig = context.driver_config()?; - if config.state_dir.as_os_str().is_empty() { - config.state_dir = openshell_driver_vm::VmComputeConfig::default_state_dir(); - } - Ok(config) -} - #[cfg(all( not(target_os = "windows"), - any( - feature = "compute-driver-docker", - feature = "compute-driver-podman", - feature = "compute-driver-vm" - ) + any(feature = "compute-driver-docker", feature = "compute-driver-podman") ))] fn require_guest_tls_for_local_driver( context: &openshell_server::ComputeDriverBuildContext<'_>, @@ -481,11 +396,7 @@ fn require_guest_tls_for_local_driver( #[cfg(all( not(target_os = "windows"), - any( - feature = "compute-driver-docker", - feature = "compute-driver-podman", - feature = "compute-driver-vm" - ) + any(feature = "compute-driver-docker", feature = "compute-driver-podman") ))] fn validate_local_driver_guest_tls( gateway_tls_enabled: bool, @@ -502,11 +413,7 @@ fn validate_local_driver_guest_tls( #[cfg(all( not(target_os = "windows"), - any( - feature = "compute-driver-docker", - feature = "compute-driver-podman", - feature = "compute-driver-vm" - ) + any(feature = "compute-driver-docker", feature = "compute-driver-podman") ))] fn apply_guest_tls( ca: &mut Option, @@ -528,11 +435,7 @@ fn apply_guest_tls( #[cfg(all( test, not(target_os = "windows"), - any( - feature = "compute-driver-docker", - feature = "compute-driver-podman", - feature = "compute-driver-vm" - ) + any(feature = "compute-driver-docker", feature = "compute-driver-podman") ))] mod local_driver_tests { use super::{apply_guest_tls, validate_local_driver_guest_tls}; @@ -541,17 +444,16 @@ mod local_driver_tests { #[test] #[cfg(feature = "in-tree-compute-drivers")] fn linux_builtin_compute_driver_registry_has_expected_names() { - assert_eq!( - super::install_default_compute_drivers() - .installed_driver_names() - .collect::>(), - ["docker", "kubernetes", "podman", "vm"] - ); + let registry = super::install_default_compute_drivers(); + assert_eq!(registry.installed_driver_names().count(), 4); + for name in ["docker", "kubernetes", "podman"] { + assert!(registry.installed_driver_names().any(|item| item == name)); + } } #[test] fn tls_enabled_local_drivers_require_a_guest_bundle() { - for driver_name in ["docker", "podman", "vm"] { + for driver_name in ["docker", "podman"] { let error = validate_local_driver_guest_tls(true, false, driver_name) .expect_err("TLS-enabled local driver must require guest TLS"); let message = error.to_string(); @@ -620,14 +522,17 @@ mod tests { "mxc", #[cfg(feature = "compute-driver-podman")] "podman", - #[cfg(feature = "compute-driver-vm")] - "vm", ]; + let registry = install_default_compute_drivers(); + #[cfg(all(not(target_os = "windows"), feature = "compute-driver-managed"))] assert_eq!( - install_default_compute_drivers() - .installed_driver_names() - .collect::>(), - expected + registry.installed_driver_names().count(), + expected.len() + 1 ); + #[cfg(not(all(not(target_os = "windows"), feature = "compute-driver-managed")))] + assert_eq!(registry.installed_driver_names().count(), expected.len()); + for name in expected { + assert!(registry.installed_driver_names().any(|item| item == name)); + } } }