Restrict launches to prepared runtime images

This commit is contained in:
2026-10-05 23:26:18 -04:00
parent 3952c4c938
commit f8ac2436b5
11 changed files with 161 additions and 29 deletions
+10 -12
View File
@@ -337,37 +337,35 @@ describe('automatic host preparation', () => {
expect(libraries.requireReadyLibrary(id, old.id).id).toBe(old.id);
});
it.each([false, true])('prepares queued runtime and library versions after newer publication (automatic placement: %s)', async automatic => {
it.each([false, true])('prepares queued libraries without pulling custom runtimes (automatic placement: %s)', async automatic => {
const old = await publish();
const id = enroll({ runtimeImages: [runtime] });
const other = enroll({ runtimeImages: [runtime] });
const image = 'ghcr.io/example/plutainer:queued-version';
const id = enroll({ runtimeImages: [runtime, image] });
const other = enroll({ runtimeImages: [runtime] });
const { queueNativeCreation } = await import('../../src/lib/server/provisioning/native.js');
for (let index = 0; index < 2; index++) {
expect(queueNativeCreation({ game: 't5', gameMode: 'zombie', mapSlug: 'theater', libraryId: old.id,
hostId: automatic ? undefined : id, image, plutoKey: 'saved-key' }).ok).toBe(true);
}
// Also cover pre-existing intents whose image is no longer cached.
heartbeat(id, { runtimeImages: [runtime] });
const latest = await publish();
const status = preparation.hostPreparationStatus(hosts.getHost(id)!);
expect(status.items.filter(item => item.label === image)).toHaveLength(1);
expect(status.items.filter(item => item.label === image)).toHaveLength(0);
expect(status.items.filter(item => item.label.includes('Queued version'))).toHaveLength(1);
const otherStatus = preparation.hostPreparationStatus(hosts.getHost(other)!);
expect(otherStatus.items.some(item => item.label === image)).toBe(automatic);
expect(otherStatus.items.some(item => item.label === image)).toBe(false);
expect(otherStatus.items.some(item => item.label.includes('Queued version'))).toBe(automatic);
expect(operations(id)).toEqual([]);
preparation.reconcileHostPreparation(id);
const pull = operations(id)[0];
expect(pull).toMatchObject({ kind: 'runtime.pull', payload: { image } });
vi.setSystemTime(Date.now() + 1);
hosts.reportHostOperation(id, pull.id, { state: 'completed' });
heartbeat(id, { runtimeImages: [runtime, image] });
preparation.reconcileHostPreparation(id);
const download = operations(id)[0];
expect(download).toMatchObject({ kind: 'library.download', payload: { libraryVersionId: old.id, automatic: true } });
preparation.reconcileHostPreparation(id);
expect(hosts.getHostOperation(download.id)?.state).toBe('pending');
expect(operations(id)).toHaveLength(2);
expect(operations(id)).toHaveLength(1);
preparation.reconcileHostPreparation(other);
expect([...operations(id), ...operations(other)].every(operation => operation.kind !== 'runtime.pull')).toBe(true);
verified(id, old);
vi.setSystemTime(Date.now() + 1);