Pool Plutonium keys by game for launches and autoscaling

This commit is contained in:
2026-10-07 11:24:53 -04:00
parent 56e5a2d06d
commit ec45371478
47 changed files with 956 additions and 305 deletions
+26 -8
View File
@@ -90,19 +90,36 @@ describe('multi-server blueprints', () => {
describe('atomic collection launches', () => {
it('counts available keys once per game across all maps without reserving them in a preview', async () => {
const { blueprint } = await previewLineup();
const { saveServerKey, reserveServerKey } = await import('../../src/lib/server/fleet/server-keys.js');
const { previewCollection, queueCollection } = await import('../../src/lib/server/fleet/collections.js');
const { db } = await import('../../src/lib/server/db/index.js');
saveServerKey('t5', 'pool-one'); saveServerKey('t5', 'pool-two'); saveServerKey('t6', 'pool-three');
const preview = previewCollection(blueprint.id);
expect(preview.servers.map(server => server.hasSavedKey)).toEqual([true, true, false, true, false]);
expect(db().prepare('SELECT * FROM server_key_leases').all()).toEqual([]);
reserveServerKey('t5_zombie_pentagon_1', 't5');
expect(() => queueCollection({ previewId: preview.id })).toThrow(/No available T5 server keys/);
expect(db().prepare('SELECT * FROM server_creation_jobs').all()).toEqual([]);
expect(db().prepare('SELECT * FROM server_key_leases').all()).toHaveLength(1);
});
it('rolls back all earlier jobs and key changes if the final server has no valid key', async () => {
const { preview } = await previewLineup();
const { queueCollection } = await import('../../src/lib/server/fleet/collections.js');
const { db } = await import('../../src/lib/server/db/index.js');
const { saveServerKey, getServerKey } = await import('../../src/lib/server/fleet/server-keys.js');
saveServerKey(preview.servers[0].name!, 'original-key');
saveServerKey('t5', 'original-key');
const keys = keysFor(preview);
delete keys[preview.servers[4].name!];
expect(() => queueCollection({ previewId: preview.id, keys })).toThrow(/Save a Plutonium server key/);
expect(() => queueCollection({ previewId: preview.id, keys })).toThrow(/No available .* server keys/);
for (const table of ['server_launch_collections', 'server_collection_members', 'server_creation_jobs', 'server_deployments', 'server_launch_previews', 'server_launch_recipes']) {
expect(db().prepare(`SELECT * FROM ${table}`).all()).toEqual([]);
}
expect(getServerKey(preview.servers[0].name!)).toBe('original-key');
expect(getServerKey(preview.servers[0].name!)).toBeUndefined();
expect(db().prepare('SELECT pluto_key FROM server_keys').get()).toEqual({ pluto_key: 'original-key' });
expect(db().prepare('SELECT * FROM server_key_leases').all()).toEqual([]);
expect(db().prepare('SELECT * FROM server_keys').all()).toHaveLength(1);
keys[preview.servers[4].name!] = 'invalid key';
expect(() => queueCollection({ previewId: preview.id, keys })).toThrow(/without whitespace/);
@@ -115,7 +132,7 @@ describe('atomic collection launches', () => {
const { queueCollection, previewCollection, getCollection } = await import('../../src/lib/server/fleet/collections.js');
const { saveServerKey } = await import('../../src/lib/server/fleet/server-keys.js');
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
for (const server of preview.servers) saveServerKey(server.name!, `saved-${server.name}`);
for (const server of preview.servers) saveServerKey(server.name!.slice(0, 2), `saved-${server.name}`);
const ready = previewCollection(blueprint.id);
expect(ready.servers.every(server => server.hasSavedKey)).toBe(true);
const queued = queueCollection({ previewId: ready.id });
@@ -171,17 +188,18 @@ describe('matching a blueprint exactly', () => {
expect(db().prepare('SELECT state FROM server_deployments WHERE id = ?').get(first.deploymentId)).toEqual({ state: 'queued' });
expect(getCollection(queued.collectionId!)?.collection).toMatchObject({ done: 1, total: 1, active: 0 });
const { getServerKey } = await import('../../src/lib/server/fleet/server-keys.js');
expect(getServerKey(second.name)).toBe('second');
expect(getServerKey(second.name)).toBeUndefined();
expect(db().prepare("SELECT pluto_key FROM server_keys WHERE pluto_key='second'").get()).toEqual({ pluto_key: 'second' });
});
it('rolls back removals and creation cancellations when a required key is missing', async () => {
it('rolls back removals and creation cancellations when the game pool is exhausted', async () => {
const { queueCreateServer } = await import('../../src/lib/server/fleet/create.js');
const old = queueCreateServer({ gameMode: 'zombie', mapSlug: 'moon', plutoKey: 'existing' });
if (!old.ok) throw new Error(old.message);
const plan = await blueprint([{ game: 't5', mapSlug: 'theater', count: 1 }]);
const plan = await blueprint([{ game: 't5', mapSlug: 'theater', count: 2 }]);
const { previewCollection, queueCollection } = await import('../../src/lib/server/fleet/collections.js');
const preview = previewCollection(plan.id, 'exact');
expect(() => queueCollection({ previewId: preview.id })).toThrow(/Save a Plutonium server key/);
expect(() => queueCollection({ previewId: preview.id })).toThrow(/No available .* server keys/);
const { db } = await import('../../src/lib/server/db/index.js');
expect(db().prepare('SELECT state FROM server_deployments WHERE id = ?').get(old.deploymentId)).toEqual({ state: 'queued' });
for (const table of ['server_collection_removals', 'server_launch_collections', 'server_creation_dependencies']) expect(db().prepare(`SELECT * FROM ${table}`).all()).toEqual([]);