Skip to content
Open
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
35 changes: 22 additions & 13 deletions bin/lib/nim.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,21 @@ function startNimContainerByName(name, model, port = 8000) {

function waitForNimHealth(port = 8000, timeout = 300) {
const start = Date.now();
const _interval = 5000;
const safePort = Number(port);
console.log(` Waiting for NIM health on port ${safePort} (timeout: ${timeout}s)...`);
const intervalSec = 5;
const hostPort = Number(port);
console.log(` Waiting for NIM health on port ${hostPort} (timeout: ${timeout}s)...`);

while ((Date.now() - start) / 1000 < timeout) {
try {
const result = runCapture(`curl -sf http://localhost:${safePort}/v1/models`, {
const result = runCapture(`curl -sf http://localhost:${hostPort}/v1/models`, {
ignoreError: true,
});
if (result) {
console.log(" NIM is healthy.");
return true;
}
} catch { /* ignored */ }
// Synchronous sleep via spawnSync
require("child_process").spawnSync("sleep", ["5"]);
require("child_process").spawnSync("sleep", [String(intervalSec)]);
}
console.error(` NIM did not become healthy within ${timeout}s.`);
return false;
Expand All @@ -183,24 +182,34 @@ function stopNimContainerByName(name) {
run(`docker rm ${qn} 2>/dev/null || true`, { ignoreError: true });
}

function nimStatus(sandboxName) {
function nimStatus(sandboxName, port) {
const name = containerName(sandboxName);
return nimStatusByName(name);
return nimStatusByName(name, port);
}

function nimStatusByName(name) {
function nimStatusByName(name, port) {
try {
const qn = shellQuote(name);
const state = runCapture(
`docker inspect --format '{{.State.Status}}' ${shellQuote(name)} 2>/dev/null`,
`docker inspect --format '{{.State.Status}}' ${qn} 2>/dev/null`,
{ ignoreError: true }
);
if (!state) return { running: false, container: name };

let healthy = false;
if (state === "running") {
const health = runCapture(`curl -sf http://localhost:8000/v1/models 2>/dev/null`, {
ignoreError: true,
});
let resolvedHostPort = port != null ? Number(port) : 0;
if (!resolvedHostPort) {
const mapping = runCapture(`docker port ${qn} 8000 2>/dev/null`, {
ignoreError: true,
});
const m = mapping && mapping.match(/:(\d+)\s*$/);
resolvedHostPort = m ? Number(m[1]) : 8000;
}
const health = runCapture(
`curl -sf http://localhost:${resolvedHostPort}/v1/models 2>/dev/null`,
{ ignoreError: true }
);
healthy = !!health;
}
return { running: state === "running", healthy, container: name, state };
Expand Down
113 changes: 112 additions & 1 deletion test/nim.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect } from "vitest";
import { createRequire } from "module";
import { describe, it, expect, vi } from "vitest";
import nim from "../bin/lib/nim";

const require = createRequire(import.meta.url);
const NIM_PATH = require.resolve("../bin/lib/nim");
const RUNNER_PATH = require.resolve("../bin/lib/runner");

function loadNimWithMockedRunner(runCapture) {
const runner = require(RUNNER_PATH);
const originalRun = runner.run;
const originalRunCapture = runner.runCapture;

delete require.cache[NIM_PATH];
runner.run = vi.fn();
runner.runCapture = runCapture;
const nimModule = require(NIM_PATH);

return {
nimModule,
restore() {
delete require.cache[NIM_PATH];
runner.run = originalRun;
runner.runCapture = originalRunCapture;
},
};
}

describe("nim", () => {
describe("listModels", () => {
it("returns 5 models", () => {
Expand Down Expand Up @@ -69,4 +94,90 @@ describe("nim", () => {
expect(st.running).toBe(false);
});
});

describe("nimStatusByName", () => {
it("uses provided port directly", () => {
const runCapture = vi.fn((cmd) => {
if (cmd.includes("docker inspect")) return "running";
if (cmd.includes("http://localhost:9000/v1/models")) return '{"data":[]}';
return "";
});
const { nimModule, restore } = loadNimWithMockedRunner(runCapture);

try {
const st = nimModule.nimStatusByName("foo", 9000);
const commands = runCapture.mock.calls.map(([cmd]) => cmd);

expect(st).toMatchObject({ running: true, healthy: true, container: "foo", state: "running" });
expect(commands.some((cmd) => cmd.includes("docker port"))).toBe(false);
expect(commands.some((cmd) => cmd.includes("http://localhost:9000/v1/models"))).toBe(true);
} finally {
restore();
}
});

it("uses published docker port when no port is provided", () => {
for (const mapping of ["0.0.0.0:9000", "127.0.0.1:9000", "[::]:9000", ":::9000"]) {
const runCapture = vi.fn((cmd) => {
if (cmd.includes("docker inspect")) return "running";
if (cmd.includes("docker port")) return mapping;
if (cmd.includes("http://localhost:9000/v1/models")) return '{"data":[]}';
return "";
});
const { nimModule, restore } = loadNimWithMockedRunner(runCapture);

try {
const st = nimModule.nimStatusByName("foo");
const commands = runCapture.mock.calls.map(([cmd]) => cmd);

expect(st).toMatchObject({ running: true, healthy: true, container: "foo", state: "running" });
expect(commands.some((cmd) => cmd.includes("docker port"))).toBe(true);
expect(commands.some((cmd) => cmd.includes("http://localhost:9000/v1/models"))).toBe(true);
} finally {
restore();
}
}
});

it("falls back to 8000 when docker port lookup fails", () => {
const runCapture = vi.fn((cmd) => {
if (cmd.includes("docker inspect")) return "running";
if (cmd.includes("docker port")) return "";
if (cmd.includes("http://localhost:8000/v1/models")) return '{"data":[]}';
return "";
});
const { nimModule, restore } = loadNimWithMockedRunner(runCapture);

try {
const st = nimModule.nimStatusByName("foo");
const commands = runCapture.mock.calls.map(([cmd]) => cmd);

expect(st).toMatchObject({ running: true, healthy: true, container: "foo", state: "running" });
expect(commands.some((cmd) => cmd.includes("docker port"))).toBe(true);
expect(commands.some((cmd) => cmd.includes("http://localhost:8000/v1/models"))).toBe(true);
} finally {
restore();
}
});

it("does not run health check when container is not running", () => {
const runCapture = vi.fn((cmd) => {
if (cmd.includes("docker inspect")) return "exited";
return "";
});
const { nimModule, restore } = loadNimWithMockedRunner(runCapture);

try {
const st = nimModule.nimStatusByName("foo");
const commands = runCapture.mock.calls.map(([cmd]) => cmd);

expect(st).toMatchObject({ running: false, healthy: false, container: "foo", state: "exited" });
expect(commands).toHaveLength(1);
expect(commands.some((cmd) => cmd.includes("docker port"))).toBe(false);
expect(commands.some((cmd) => cmd.includes("http://localhost:"))).toBe(false);
} finally {
restore();
}
});
});
});
Loading