Skip to content

Commit 95e8bcb

Browse files
committed
fix(podman): discover CDI GPUs from server info
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 67adcf1 commit 95e8bcb

7 files changed

Lines changed: 298 additions & 23 deletions

File tree

‎crates/openshell-driver-podman/README.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,21 @@ authenticated health signal.
149149

150150
Gateway OTLP configuration continues to export compute-driver spans under the
151151
`openshell-driver-podman` service, preserving gateway trace context.
152+
153+
## CDI GPU discovery
154+
155+
The driver reads `host.cdiSpecDirs` and `host.discoveredDevices` from
156+
`GET /v5.0.0/libpod/info` during initialization and before each default GPU
157+
preflight or create selection. Only devices with `source: "cdi"` enter the CDI
158+
inventory. The shared GPU selector retains its NVIDIA class and count policy;
159+
collecting other CDI IDs does not add GPU workload support for other vendors.
160+
161+
Presence of either CDI field means server discovery is authoritative. Podman
162+
can omit `discoveredDevices` when its inventory is empty while still reporting
163+
`cdiSpecDirs`; this yields an empty inventory rather than probing local devices.
164+
When both fields are absent, legacy discovery scans local `/dev/nvidia<N>`
165+
paths and retains the `/dev/dxg` all-device fallback. Spec directories are
166+
server paths for diagnostics, not directories to scan on the gateway host.
167+
Refresh API or decoding failures fail the default GPU request without updating
168+
the selector or selecting from stale inventory. Explicit CDI selectors continue
169+
to be validated and passed to Podman without inventory lookup.

‎crates/openshell-driver-podman/src/client.rs‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ pub struct SystemInfo {
311311
#[derive(Debug, Clone, serde::Deserialize)]
312312
#[serde(rename_all = "camelCase")]
313313
pub struct HostInfo {
314+
/// Presence marks servers that expose CDI discovery, including empty inventories.
315+
#[serde(default)]
316+
pub cdi_spec_dirs: Option<Vec<String>>,
317+
#[serde(default)]
318+
pub discovered_devices: Option<Vec<DiscoveredDevice>>,
314319
#[serde(default)]
315320
pub cgroup_version: String,
316321
#[serde(default)]
@@ -321,6 +326,13 @@ pub struct HostInfo {
321326
pub security: SecurityInfo,
322327
}
323328

329+
/// A device discovered by the Podman server, independent of GPU selection policy.
330+
#[derive(Debug, Clone, serde::Deserialize)]
331+
pub struct DiscoveredDevice {
332+
pub source: String,
333+
pub id: String,
334+
}
335+
324336
/// Security-related fields from the Podman system info response.
325337
///
326338
/// Podman returns `host.security.rootless: true` when the daemon is
@@ -1158,6 +1170,41 @@ mod tests {
11581170
assert!(validate_name(&exact_name).is_ok());
11591171
}
11601172

1173+
#[tokio::test]
1174+
async fn system_info_retains_cdi_inventory_and_spec_directories() {
1175+
let (socket, requests, handle) = spawn_podman_stub(
1176+
"info-cdi",
1177+
vec![
1178+
StubResponse::new(
1179+
StatusCode::OK,
1180+
r#"{"host":{"cdiSpecDirs":["/etc/cdi","/var/run/cdi"],"discoveredDevices":[{"source":"cdi","id":"example.com/gpu=test0"}]}}"#,
1181+
),
1182+
StubResponse::new(StatusCode::OK, r#"{"host":{"cdiSpecDirs":["/etc/cdi"]}}"#),
1183+
StubResponse::new(StatusCode::OK, r#"{"host":{}}"#),
1184+
],
1185+
);
1186+
let client = PodmanClient::new(socket);
1187+
let host = client.system_info().await.unwrap().host;
1188+
assert_eq!(
1189+
host.cdi_spec_dirs.unwrap(),
1190+
vec!["/etc/cdi", "/var/run/cdi"]
1191+
);
1192+
let devices = host.discovered_devices.unwrap();
1193+
assert_eq!(devices[0].source, "cdi");
1194+
assert_eq!(devices[0].id, "example.com/gpu=test0");
1195+
let empty = client.system_info().await.unwrap().host;
1196+
assert!(empty.cdi_spec_dirs.is_some());
1197+
assert!(empty.discovered_devices.is_none());
1198+
let legacy = client.system_info().await.unwrap().host;
1199+
assert!(legacy.cdi_spec_dirs.is_none());
1200+
assert!(legacy.discovered_devices.is_none());
1201+
handle.await.unwrap();
1202+
assert_eq!(
1203+
*requests.lock().unwrap(),
1204+
vec!["GET /v5.0.0/libpod/info"; 3]
1205+
);
1206+
}
1207+
11611208
#[test]
11621209
fn system_info_parses_rootless_network_helper() {
11631210
let info: SystemInfo = serde_json::from_str(

0 commit comments

Comments
 (0)