Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/openshell-driver-podman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,21 @@ authenticated health signal.

Gateway OTLP configuration continues to export compute-driver spans under the
`openshell-driver-podman` service, preserving gateway trace context.

## CDI GPU discovery

The driver reads `host.cdiSpecDirs` and `host.discoveredDevices` from
`GET /v5.0.0/libpod/info` during initialization and before each default GPU
preflight or create selection. Only devices with `source: "cdi"` enter the CDI
inventory. The shared GPU selector retains its NVIDIA class and count policy;
collecting other CDI IDs does not add GPU workload support for other vendors.

Presence of either CDI field means server discovery is authoritative. Podman
can omit `discoveredDevices` when its inventory is empty while still reporting
`cdiSpecDirs`; this yields an empty inventory rather than probing local devices.
When both fields are absent, legacy discovery scans local `/dev/nvidia<N>`
paths and retains the `/dev/dxg` all-device fallback. Spec directories are
server paths for diagnostics, not directories to scan on the gateway host.
Refresh API or decoding failures fail the default GPU request without updating
the selector or selecting from stale inventory. Explicit CDI selectors continue
to be validated and passed to Podman without inventory lookup.
47 changes: 47 additions & 0 deletions crates/openshell-driver-podman/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ pub struct SystemInfo {
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HostInfo {
/// Presence marks servers that expose CDI discovery, including empty inventories.
#[serde(default)]
pub cdi_spec_dirs: Option<Vec<String>>,
#[serde(default)]
pub discovered_devices: Option<Vec<DiscoveredDevice>>,
#[serde(default)]
pub cgroup_version: String,
#[serde(default)]
Expand All @@ -321,6 +326,13 @@ pub struct HostInfo {
pub security: SecurityInfo,
}

/// A device discovered by the Podman server, independent of GPU selection policy.
#[derive(Debug, Clone, serde::Deserialize)]
pub struct DiscoveredDevice {
pub source: String,
pub id: String,
}

/// Security-related fields from the Podman system info response.
///
/// Podman returns `host.security.rootless: true` when the daemon is
Expand Down Expand Up @@ -1158,6 +1170,41 @@ mod tests {
assert!(validate_name(&exact_name).is_ok());
}

#[tokio::test]
async fn system_info_retains_cdi_inventory_and_spec_directories() {
let (socket, requests, handle) = spawn_podman_stub(
"info-cdi",
vec![
StubResponse::new(
StatusCode::OK,
r#"{"host":{"cdiSpecDirs":["/etc/cdi","/var/run/cdi"],"discoveredDevices":[{"source":"cdi","id":"example.com/gpu=test0"}]}}"#,
),
StubResponse::new(StatusCode::OK, r#"{"host":{"cdiSpecDirs":["/etc/cdi"]}}"#),
StubResponse::new(StatusCode::OK, r#"{"host":{}}"#),
],
);
let client = PodmanClient::new(socket);
let host = client.system_info().await.unwrap().host;
assert_eq!(
host.cdi_spec_dirs.unwrap(),
vec!["/etc/cdi", "/var/run/cdi"]
);
let devices = host.discovered_devices.unwrap();
assert_eq!(devices[0].source, "cdi");
assert_eq!(devices[0].id, "example.com/gpu=test0");
let empty = client.system_info().await.unwrap().host;
assert!(empty.cdi_spec_dirs.is_some());
assert!(empty.discovered_devices.is_none());
let legacy = client.system_info().await.unwrap().host;
assert!(legacy.cdi_spec_dirs.is_none());
assert!(legacy.discovered_devices.is_none());
handle.await.unwrap();
assert_eq!(
*requests.lock().unwrap(),
vec!["GET /v5.0.0/libpod/info"; 3]
);
}

#[test]
fn system_info_parses_rootless_network_helper() {
let info: SystemInfo = serde_json::from_str(
Expand Down
Loading
Loading