Files
netris-nestri/packages/core/src/box/location.ts
T
Wanjohi 5a399686ca fix(api): rooms book cards atomically, free never gets a non-entry card, slots identify GPUs
Create a room under a per-user advisory lock (limit, card and insert in one
transaction) and book cards under per-machine locks. A free plan with no model
is placed on an entry card only. Boxes with no card hold their machine's first
card; the new migration backfills single-GPU fleet boxes and adds
box.gpu_slot. Machine.Gpu accepts slot and renderNode; session.start carries
gpuSlot and gpuPciId. A stopped room re-checks its card when a run is
requested.
2026-10-19 22:07:20 +03:00

82 lines
2.9 KiB
TypeScript

import z from 'zod';
import { Rooms } from '../billing/rooms.js';
import { Env } from '../env.js';
import { fn } from '../fn.js';
import { Machine } from '../machine/index.js';
import { Placement } from './placement.js';
/**
* Where a room can go: one GPU each. A person's own are listed one by one;
* the fleet's are grouped by model, because which machine serves a model is
* placement's choice and not theirs.
*/
export namespace Location {
export const Own = z
.object({
machineId: z.string().meta({ description: 'The host the card is in' }),
pciId: z.string().meta({ description: 'vendor:device, as the host reported it' }),
slot: z.string().nullable().meta({
description: 'PCI address on the host, when it reports one. Tells apart cards of one model'
}),
model: z.string().meta({ description: 'What the card is called' }),
vramMib: z.number().int().nullable().meta({ description: 'Card memory in MiB, if reported' }),
online: z.boolean().meta({ description: 'Whether its host is reporting in' })
})
.meta({ ref: 'OwnLocation' });
export const Fleet = z
.object({
model: z.string().meta({ description: 'The card, as a person recognises it' }),
vramMib: z.number().int().nullable().meta({ description: 'Card memory in MiB, if reported' }),
available: z.boolean().meta({ description: 'Whether one is free to put a room on now' }),
entry: z.boolean().meta({
description: 'Whether this is an entry model, open to every plan'
}),
allowed: z.boolean().meta({ description: 'Whether your plan may create a room on it' })
})
.meta({ ref: 'FleetLocation' });
export const Info = z.object({ own: z.array(Own), fleet: z.array(Fleet) }).meta({
ref: 'Locations',
description: 'Every GPU a room could be created on'
});
export type Info = z.infer<typeof Info>;
export const list = fn(
z.object({ userId: z.string(), plan: z.string().nullable().optional() }),
async (input): Promise<Info> => {
const own = (await Machine.listByOwner(input.userId)).flatMap((m) =>
(m.gpus ?? []).map((g) => ({
machineId: m.id,
pciId: g.pciId,
slot: g.slot ?? null,
model: g.model,
vramMib: g.vramMib ?? null,
online: Machine.isOnline(m.lastSeen ?? null)
}))
);
const organisationId = Env.get().FLEET_ORGANISATION_ID;
const byModel = new Map<string, Info['fleet'][number]>();
if (organisationId) {
for (const card of await Placement.fleetCards(organisationId)) {
const seen = byModel.get(card.model);
byModel.set(card.model, {
model: card.model,
vramMib: Math.max(seen?.vramMib ?? 0, card.vramMib ?? 0) || null,
available: (seen?.available ?? false) || card.free,
entry: Rooms.isEntry(card.model),
allowed: Rooms.mayUseModel(input.plan, card.model)
});
}
}
return {
own,
fleet: [...byModel.values()].sort((a, b) => a.model.localeCompare(b.model))
};
}
);
}