gpu-tenancy.mjs judges permission, then the one slot, then one host free-VRAM read per admission. The permission is a gamecult.vili.gpu_permission.v1 document in vili.service.cc, set only from loopback at POST /gpu-permission; absent means not allowed. The daemon holds one CultCache instance. Tests pin the gate with fake ports and the daemon edge over HTTP, including the slot wiring through acquire() (finding s2 slot-wiring-unpinned). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
7.4 KiB
JavaScript
167 lines
7.4 KiB
JavaScript
// Boots the real daemon and observes the gate where it is decided: the HTTP edge. Needs the
|
|
// daemon's own dependencies (ws, cultcache-ts), so it runs on a machine that has them. Every boot
|
|
// sets VILI_GPU_MIN_FREE_MIB=999999 so an allowed request ends in a VRAM refusal and never starts
|
|
// the worker: no test here may start a GPU job.
|
|
import assert from "node:assert/strict";
|
|
import { spawn } from "node:child_process";
|
|
import { mkdtemp, readdir, rm } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const daemonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "vili-daemon.mjs");
|
|
let nextPort = 18000 + Math.floor(Math.random() * 1000);
|
|
|
|
async function bootDaemon(stateRoot, { host = "127.0.0.1", env = { VILI_GPU_MIN_FREE_MIB: "999999" } } = {}) {
|
|
const port = nextPort++;
|
|
const child = spawn(process.execPath, [daemonPath, "--host", host, "--port", String(port), "--state-root", stateRoot], {
|
|
env: { ...process.env, ...env },
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
windowsHide: true,
|
|
});
|
|
let output = "";
|
|
child.stdout.on("data", (chunk) => { output += chunk; });
|
|
child.stderr.on("data", (chunk) => { output += chunk; });
|
|
const deadline = Date.now() + 30000;
|
|
while (!output.includes("Vili listening")) {
|
|
if (child.exitCode !== null || Date.now() > deadline) throw new Error(`daemon did not start: ${output.slice(-500)}`);
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
}
|
|
const stop = () => new Promise((resolve) => {
|
|
if (child.exitCode !== null) resolve();
|
|
else { child.once("exit", resolve); child.kill(); }
|
|
});
|
|
return { child, port, stop };
|
|
}
|
|
|
|
const url = (d, route, host = "127.0.0.1") => `http://${host}:${d.port}${route}`;
|
|
const generate = (d, body) => fetch(url(d, "/motion/generate"), {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const setPermission = (d, body, host = "127.0.0.1") => fetch(url(d, "/gpu-permission", host), {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const readPermission = async (d) => (await fetch(url(d, "/gpu-permission"))).json();
|
|
|
|
test("http: refusal at the daemon edge", async () => {
|
|
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-"));
|
|
const d = await bootDaemon(stateRoot);
|
|
try {
|
|
assert.deepEqual(await readPermission(d), { allowed: false, changedAt: null, jobRunning: false, lastRefusal: null });
|
|
assert.equal((await generate(d, {})).status, 400, "request-shape errors come ahead of the gate");
|
|
|
|
const refused = await generate(d, { prompt: "a person waves hello, never seen before" });
|
|
assert.equal(refused.status, 503);
|
|
assert.equal(refused.headers.get("retry-after"), "60");
|
|
assert.deepEqual(await refused.json(), { ok: false, code: "gpu-busy", source: "household", gpu: { allowed: false, changedAt: null } });
|
|
|
|
assert.deepEqual(await readdir(path.join(stateRoot, "jobs")), [], "no job file");
|
|
assert.equal((await readdir(stateRoot)).includes("artifacts"), false, "no artifact directory");
|
|
|
|
const state = await (await fetch(url(d, "/operator-state"))).json();
|
|
assert.equal(state.gpu.lastRefusal.source, "household");
|
|
assert.equal(state.gpu.allowed, false);
|
|
} finally {
|
|
await d.stop();
|
|
await rm(stateRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("http: the permission persists across a daemon restart", async () => {
|
|
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-persist-"));
|
|
let d = await bootDaemon(stateRoot);
|
|
try {
|
|
const on = await setPermission(d, { allowed: true });
|
|
assert.equal(on.status, 200);
|
|
const onBody = await on.json();
|
|
assert.equal(onBody.ok, true);
|
|
assert.equal(onBody.gpu.allowed, true);
|
|
const before = await readPermission(d);
|
|
assert.equal(before.allowed, true);
|
|
assert.ok(before.changedAt);
|
|
|
|
assert.equal((await setPermission(d, { allowed: "yes" })).status, 400);
|
|
assert.equal((await setPermission(d, {})).status, 400);
|
|
assert.deepEqual(await readPermission(d), before, "a refused write changes nothing");
|
|
|
|
await d.stop();
|
|
d = await bootDaemon(stateRoot);
|
|
const after = await readPermission(d);
|
|
assert.equal(after.allowed, true);
|
|
assert.equal(after.changedAt, before.changedAt);
|
|
|
|
await setPermission(d, { allowed: false });
|
|
await d.stop();
|
|
d = await bootDaemon(stateRoot);
|
|
assert.equal((await readPermission(d)).allowed, false, "off persists too");
|
|
} finally {
|
|
await d.stop();
|
|
await rm(stateRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("http: an allowed request reaches the slot and the VRAM read", async () => {
|
|
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-slot-"));
|
|
const d = await bootDaemon(stateRoot);
|
|
try {
|
|
await setPermission(d, { allowed: true });
|
|
const refused = await generate(d, { prompt: "a person waves hello, never seen before" });
|
|
assert.equal(refused.status, 503);
|
|
const body = await refused.json();
|
|
assert.equal(body.source, "vram", "admitted through acquire(), then refused for VRAM");
|
|
assert.equal(body.minFreeMiB, 999999);
|
|
assert.deepEqual(await readdir(path.join(stateRoot, "jobs")), [], "no job file");
|
|
assert.equal((await readdir(stateRoot)).includes("artifacts"), false);
|
|
const state = await readPermission(d);
|
|
assert.equal(state.lastRefusal.source, "vram");
|
|
assert.equal(state.jobRunning, false);
|
|
} finally {
|
|
await d.stop();
|
|
await rm(stateRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("http: the switch is the household's", async () => {
|
|
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-lan-"));
|
|
const d = await bootDaemon(stateRoot, { host: "0.0.0.0" });
|
|
try {
|
|
const lan = Object.values(os.networkInterfaces()).flat().find((i) => i.family === "IPv4" && !i.internal)?.address;
|
|
assert.ok(lan, "the host has a non-loopback IPv4 address");
|
|
const before = await readPermission(d);
|
|
const refused = await setPermission(d, { allowed: true }, lan);
|
|
assert.equal(refused.status, 403);
|
|
assert.deepEqual(await refused.json(), { ok: false, code: "household-only" });
|
|
assert.deepEqual(await readPermission(d), before, "GET still reads the previous value");
|
|
assert.equal((await setPermission(d, { allowed: true })).status, 200, "loopback still sets it");
|
|
} finally {
|
|
await d.stop();
|
|
await rm(stateRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("http: a malformed override refuses to start and names the variable, not its value", async () => {
|
|
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-env-"));
|
|
const child = spawn(process.execPath, [daemonPath, "--host", "127.0.0.1", "--port", String(nextPort++), "--state-root", stateRoot], {
|
|
env: { ...process.env, VILI_GPU_MIN_FREE_MIB: "10240MiB-secret" },
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
windowsHide: true,
|
|
});
|
|
let output = "";
|
|
child.stdout.on("data", (chunk) => { output += chunk; });
|
|
child.stderr.on("data", (chunk) => { output += chunk; });
|
|
try {
|
|
const code = await new Promise((resolve) => child.on("exit", resolve));
|
|
assert.notEqual(code, 0);
|
|
assert.match(output, /VILI_GPU_MIN_FREE_MIB must be a finite non-negative number/);
|
|
assert.equal(output.includes("10240MiB-secret"), false);
|
|
assert.equal(output.includes("Vili listening"), false);
|
|
} finally {
|
|
child.kill();
|
|
await rm(stateRoot, { recursive: true, force: true });
|
|
}
|
|
});
|