Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/lib/adapters/container-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { buildSubprocessEnv } from "../subprocess-env";

export type ContainerEngineOperationScope =
| "host-doctor"
| "host-local-inference"
| "gateway-inspection"
| "managed-bootstrap"
| "sandbox-lifecycle"
Expand Down
6 changes: 5 additions & 1 deletion src/lib/adapters/podman/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
} from "./socket-authority";

export interface PodmanContainerEngineOptions {
readonly operation: "host-doctor" | "managed-bootstrap" | "sandbox-lifecycle";
readonly operation:
| "host-doctor"
| "host-local-inference"
| "managed-bootstrap"
| "sandbox-lifecycle";
readonly socketAuthority: PodmanSocketAuthority;
readonly executable?: string;
readonly capture?: ContainerEngineCommandCapture;
Expand Down
1 change: 1 addition & 0 deletions src/lib/onboard/runtime-provider/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type RuntimeProviderMutationOperation =
| "workload-cleanup";
export type RuntimeProviderContainerEngineOperation =
| "host-doctor"
| "host-local-inference"
| "gateway-inspection"
| "sandbox-lifecycle"
| "workload-cleanup";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ describe("persisted engine authority", () => {
}
});

it("persists a host-local inference engine independently of lifecycle authority", () => {
const store = createFilePersistedEngineAuthorityStore(temporaryRoot());
const inference = createPersistedEngineAuthority(
"mxc",
engine("host-local-inference"),
BINDING_SHA256,
);
const lifecycle = createPersistedEngineAuthority(
"mxc",
engine("sandbox-lifecycle"),
BINDING_SHA256,
);

expect(store.record(inference)).toEqual(inference);
expect(store.record(lifecycle)).toEqual(lifecycle);
expect(store.load("host-local-inference")).toEqual(inference);
expect(store.load("sandbox-lifecycle")).toEqual(lifecycle);
});

it.each([
{
label: "provider",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const AUTHORITY_ID = /^[a-z][a-z0-9-]{0,62}:[A-Za-z0-9._:-]{1,255}$/u;
const SHA256 = /^[a-f0-9]{64}$/u;
const OPERATIONS = new Set<ContainerEngineOperationScope>([
"host-doctor",
"host-local-inference",
"gateway-inspection",
"managed-bootstrap",
"sandbox-lifecycle",
Expand Down
51 changes: 51 additions & 0 deletions src/lib/onboard/runtime-provider/podman-gpu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";

import {
normalizeNvidiaCdiDevice,
normalizePodmanCdiInventory,
qualifyPodmanGpuAttachments,
} from "./podman-gpu";

describe("Podman GPU attachment authority", () => {
it.each([
["all", "nvidia.com/gpu=all"],
["0", "nvidia.com/gpu=0"],
["1:0", "nvidia.com/gpu=1:0"],
["GPU-deadbeef", "nvidia.com/gpu=GPU-deadbeef"],
["nvidia.com/gpu=MIG-deadbeef", "nvidia.com/gpu=MIG-deadbeef"],
[
"MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0",
"nvidia.com/gpu=MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0",
],
])("normalizes %s to canonical NVIDIA CDI identity", (requested, expected) => {
expect(normalizeNvidiaCdiDevice(requested)).toBe(expected);
});

it("qualifies only exact devices advertised by the injected endpoint", () => {
const attachments = qualifyPodmanGpuAttachments(
["nvidia.com/gpu=all", "nvidia.com/gpu=0", "nvidia.com/gpu=GPU-deadbeef"],
["0", "GPU-deadbeef"],
);

expect(attachments).toEqual([
{ kind: "cdi", device: "nvidia.com/gpu=0" },
{ kind: "cdi", device: "nvidia.com/gpu=GPU-deadbeef" },
]);
expect(Object.isFrozen(attachments)).toBe(true);
expect(Object.isFrozen(attachments[0])).toBe(true);
});

it("fails closed for missing, duplicate, raw, and malformed devices", () => {
expect(() => qualifyPodmanGpuAttachments([], ["all"])).toThrow("does not advertise");
expect(() =>
qualifyPodmanGpuAttachments(["nvidia.com/gpu=0"], ["0", "nvidia.com/gpu=0"]),
).toThrow("duplicate NVIDIA CDI device");
expect(() => normalizeNvidiaCdiDevice("/dev/nvidia0")).toThrow("safe NVIDIA CDI name");
expect(() => normalizePodmanCdiInventory(["all", "nvidia.com/gpu=all"])).toThrow(
"duplicate NVIDIA device",
);
});
});
63 changes: 63 additions & 0 deletions src/lib/onboard/runtime-provider/podman-gpu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

const NVIDIA_CDI_PREFIX = "nvidia.com/gpu=";
const CDI_DEVICE_NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/u;
const LEGACY_MIG_DEVICE_NAME = /^MIG-GPU-[A-Za-z0-9-]+\/[0-9]+\/[0-9]+$/u;

export interface PodmanGpuAttachment {
readonly kind: "cdi";
readonly device: string;
}

/**
* Normalize one NVIDIA CDI device identity without consulting host state.
* Availability is proved separately against the exact Podman endpoint's
* qualified inventory.
*/
export function normalizeNvidiaCdiDevice(requestedDevice: string): string {
const requested = requestedDevice.trim();
const device = requested.startsWith(NVIDIA_CDI_PREFIX)
? requested
: `${NVIDIA_CDI_PREFIX}${requested}`;
const name = device.slice(NVIDIA_CDI_PREFIX.length);
if (!CDI_DEVICE_NAME.test(name) && !LEGACY_MIG_DEVICE_NAME.test(name)) {
throw new Error(
"Podman GPU device must be a safe NVIDIA CDI name such as 'all', '0', '1:0', 'GPU-...', or 'MIG-...'.",
);
}
return device;
}

export function normalizePodmanCdiInventory(devices: readonly string[]): readonly string[] {
if (!Array.isArray(devices) || devices.length > 256) {
throw new Error("Podman CDI inventory is invalid or exceeds its device limit.");
}
const normalized = devices.map(normalizeNvidiaCdiDevice).sort();
if (new Set(normalized).size !== normalized.length) {
throw new Error("Podman CDI inventory contains a duplicate NVIDIA device.");
}
return Object.freeze(normalized);
}

export function qualifyPodmanGpuAttachments(
availableDevices: readonly string[],
requestedDevices: readonly string[] = ["all"],
): readonly PodmanGpuAttachment[] {
const available = new Set(normalizePodmanCdiInventory(availableDevices));
if (!Array.isArray(requestedDevices) || requestedDevices.length === 0) {
throw new Error("Podman GPU attachment requires at least one NVIDIA CDI device.");
}
const requested = requestedDevices.map(normalizeNvidiaCdiDevice);
if (new Set(requested).size !== requested.length) {
throw new Error("Podman GPU attachment contains a duplicate NVIDIA CDI device.");
}
for (const device of requested) {
if (!available.has(device)) {
throw new Error(
`Rootless Podman does not advertise the requested CDI device '${device}'. Refresh the NVIDIA CDI specification and retry.`,
);
}
}
return Object.freeze(requested.map((device) => Object.freeze({ kind: "cdi" as const, device })));
}
74 changes: 74 additions & 0 deletions src/lib/onboard/runtime-provider/podman-inference-args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";

import { translatePodmanLocalInferenceArgs } from "./podman-inference-args";

const CDI_DEVICES = [
"nvidia.com/gpu=all",
"nvidia.com/gpu=0",
"nvidia.com/gpu=1:0",
"nvidia.com/gpu=2",
"nvidia.com/gpu=GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"nvidia.com/gpu=MIG-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"nvidia.com/gpu=MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0",
] as const;

describe("Podman local inference command translation", () => {
it.each([
["all", ["nvidia.com/gpu=all"]],
["device=0", ["nvidia.com/gpu=0"]],
["device=1:0", ["nvidia.com/gpu=1:0"]],
[
"device=GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
["nvidia.com/gpu=GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"],
],
[
"device=MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0",
["nvidia.com/gpu=MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0"],
],
['"device=0,2"', ["nvidia.com/gpu=0", "nvidia.com/gpu=2"]],
])("preserves Docker selector %s as exact CDI devices", (selector, devices) => {
const translated = translatePodmanLocalInferenceArgs(
["run", "--gpus", selector, "image"],
CDI_DEVICES,
);
expect(translated.filter((value) => value.startsWith("nvidia.com/gpu="))).toEqual(devices);
expect(translated.filter((value) => value === "--device")).toHaveLength(devices.length);
expect(translated).not.toContain("--gpus");
});

it("translates the NIM and vLLM subset without Docker name-filter leakage", () => {
expect(
translatePodmanLocalInferenceArgs(
["run", "--gpus=all", "--filter", "name=^/nemoclaw-vllm$"],
CDI_DEVICES,
),
).toEqual(["run", "--device", "nvidia.com/gpu=all", "--filter", "name=^nemoclaw-vllm$"]);
expect(
translatePodmanLocalInferenceArgs(["run", "--device=nvidia.com/gpu=0", "image"], CDI_DEVICES),
).toEqual(["run", "--device", "nvidia.com/gpu=0", "image"]);
});

it("fails closed instead of dropping unsupported Docker GPU modes", () => {
expect(() =>
translatePodmanLocalInferenceArgs(
["run", "--gpus", "capabilities=compute", "image"],
CDI_DEVICES,
),
).toThrow("cannot translate Docker GPU selector");
expect(() =>
translatePodmanLocalInferenceArgs(["run", "--gpus", "device=0,0"], CDI_DEVICES),
).toThrow("duplicate NVIDIA CDI device");
expect(() =>
translatePodmanLocalInferenceArgs(["run", "--runtime", "nvidia"], CDI_DEVICES),
).toThrow("refuses Docker's NVIDIA runtime mode");
expect(() =>
translatePodmanLocalInferenceArgs(["run", "--device", "/dev/nvidia0"], CDI_DEVICES),
).toThrow("refuses raw NVIDIA device paths");
expect(() =>
translatePodmanLocalInferenceArgs(["run", "--gpus", "device=9"], CDI_DEVICES),
).toThrow("does not advertise");
});
});
128 changes: 128 additions & 0 deletions src/lib/onboard/runtime-provider/podman-inference-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { qualifyPodmanGpuAttachments } from "./podman-gpu";

const MAX_ARGUMENTS = 512;
const MAX_ARGUMENT_BYTES = 16 * 1024;

function exactArgument(value: unknown, index: number): string {
if (
typeof value !== "string" ||
value.includes("\0") ||
Buffer.byteLength(value, "utf8") > MAX_ARGUMENT_BYTES
) {
throw new Error(`Podman local inference argument ${String(index)} is invalid.`);
}
return value;
}

function stripExactDoubleQuotes(raw: string): string {
const trimmed = raw.trim();
const startsQuoted = trimmed.startsWith('"');
const endsQuoted = trimmed.endsWith('"');
if (startsQuoted !== endsQuoted || (startsQuoted && trimmed.length < 2)) {
throw new Error(`Podman local inference cannot translate Docker GPU selector '${raw}' to CDI.`);
}
return startsQuoted ? trimmed.slice(1, -1) : trimmed;
}

function translatedGpuDevices(selector: string, availableCdiDevices: readonly string[]): string[] {
const normalized = stripExactDoubleQuotes(selector);
const requested =
normalized === "all"
? ["all"]
: normalized.startsWith("device=")
? normalized.slice("device=".length).split(",")
: [];
if (requested.length === 0 || requested.some((device) => device.trim() === "")) {
throw new Error(
`Podman local inference cannot translate Docker GPU selector '${selector}' to CDI.`,
);
}
return qualifyPodmanGpuAttachments(
availableCdiDevices,
requested.map((device) => device.trim()),
).map((attachment) => attachment.device);
}

function appendGpuDevices(target: string[], devices: readonly string[]): void {
for (const device of devices) target.push("--device", device);
}

/**
* Translate the bounded Docker-compatible argument subset emitted by the
* existing NIM and vLLM launchers. NVIDIA GPU selection becomes exact CDI
* attachment; Docker-only runtime and raw-device modes fail closed.
*/
export function translatePodmanLocalInferenceArgs(
args: readonly string[],
availableCdiDevices: readonly string[],
): readonly string[] {
if (!Array.isArray(args) || args.length > MAX_ARGUMENTS) {
throw new Error("Podman local inference has too many command arguments.");
}
const source = args.map(exactArgument);
const translated: string[] = [];
for (let index = 0; index < source.length; index += 1) {
const value = source[index] ?? "";
if (value === "--gpus") {
const selector = source[index + 1];
if (selector === undefined) {
throw new Error("Podman local inference requires a GPU selector value.");
}
appendGpuDevices(translated, translatedGpuDevices(selector, availableCdiDevices));
index += 1;
continue;
}
if (value.startsWith("--gpus=")) {
appendGpuDevices(
translated,
translatedGpuDevices(value.slice("--gpus=".length), availableCdiDevices),
);
continue;
}
if (
(value === "--runtime" && source[index + 1]?.toLowerCase() === "nvidia") ||
value.toLowerCase() === "--runtime=nvidia"
) {
throw new Error(
"Podman local inference refuses Docker's NVIDIA runtime mode; an exact CDI device is required.",
);
}
if (value === "--device") {
const device = source[index + 1];
if (device === undefined) {
throw new Error("Podman local inference requires a --device value.");
}
if (/^\/dev\/nvidia/u.test(device)) {
throw new Error(
"Podman local inference refuses raw NVIDIA device paths; an exact CDI device is required.",
);
}
if (device.startsWith("nvidia.com/gpu=")) {
appendGpuDevices(translated, translatedGpuDevices(`device=${device}`, availableCdiDevices));
index += 1;
continue;
}
}
if (value.startsWith("--device=/dev/nvidia")) {
throw new Error(
"Podman local inference refuses raw NVIDIA device paths; an exact CDI device is required.",
);
}
if (value.startsWith("--device=nvidia.com/gpu=")) {
appendGpuDevices(
translated,
translatedGpuDevices(`device=${value.slice("--device=".length)}`, availableCdiDevices),
);
continue;
}
if (value.startsWith("name=^/") && value.endsWith("$")) {
translated.push(`name=^${value.slice("name=^/".length)}`);
continue;
}
translated.push(value);
}
return Object.freeze(translated);
}
Loading
Loading