Move fleet retention and concurrency controls into Settings

Persist settings in SQLite, import legacy environment values once, and apply changes to cleanup and deployment queues without restarting.

Closes #51
This commit is contained in:
2026-09-27 16:11:45 -04:00
parent fc46cb9da4
commit 4fe8613678
20 changed files with 334 additions and 54 deletions
+42 -3
View File
@@ -66,6 +66,44 @@ async function states() {
}
describe('shared creation queue', () => {
it('rechecks a lowered limit after awaiting a startup in the same pass', async () => {
await queueServers(3);
const { fleetSettings, saveFleetSettings } = await import('../../src/lib/server/settings.js');
saveFleetSettings({ ...fleetSettings(), createConcurrency: 3 });
const { processCreations } = await import('../../src/lib/server/fleet/create.js');
const { komodoCreateDeployment } = await import('../../src/lib/server/integrations/komodo.js');
vi.mocked(komodoCreateDeployment).mockImplementationOnce(async () => {
saveFleetSettings({ ...fleetSettings(), createConcurrency: 1 });
return { ok: true };
});
await processCreations();
expect(komodoCreateDeployment).toHaveBeenCalledTimes(1);
expect(await states()).toEqual(['awaiting-health', 'queued', 'queued']);
});
it('applies live increases and decreases while preserving active startups', async () => {
const jobs = await queueServers(4);
const { fleetSettings, saveFleetSettings } = await import('../../src/lib/server/settings.js');
const { processCreations } = await import('../../src/lib/server/fleet/create.js');
const { komodoInspectHealthy, komodoCreateDeployment } = await import('../../src/lib/server/integrations/komodo.js');
vi.mocked(komodoInspectHealthy).mockResolvedValue({ ok: false, reason: 'still starting' });
await processCreations();
saveFleetSettings({ ...fleetSettings(), createConcurrency: 3 });
await processCreations();
expect(await states()).toEqual(['awaiting-health', 'awaiting-health', 'awaiting-health', 'queued']);
saveFleetSettings({ ...fleetSettings(), createConcurrency: 1 });
await processCreations();
expect(komodoCreateDeployment).toHaveBeenCalledTimes(3);
expect(await states()).toEqual(['awaiting-health', 'awaiting-health', 'awaiting-health', 'queued']);
vi.mocked(komodoInspectHealthy).mockImplementation(async server => server === jobs[2].name
? { ok: false, reason: 'still starting' } : { ok: true, state: 'running' });
await processCreations();
expect(await states()).toEqual(['done', 'done', 'awaiting-health', 'queued']);
vi.mocked(komodoInspectHealthy).mockResolvedValue({ ok: true, state: 'running' });
await processCreations();
expect(await states()).toEqual(['done', 'done', 'done', 'awaiting-health']);
});
it('starts only one server per pass and holds its slot until a later health check', async () => {
const jobs = await queueServers(3);
const { processCreations } = await import('../../src/lib/server/fleet/create.js');
@@ -345,13 +383,14 @@ describe('shared creation queue', () => {
describe('creation concurrency configuration', () => {
it('defaults to one startup', async () => {
vi.stubEnv('DEPLOYMENT_CREATE_CONCURRENCY', undefined);
const { config } = await import('../../src/lib/server/config.js');
expect(config.createConcurrency).toBe(1);
const { fleetSettings } = await import('../../src/lib/server/settings.js');
expect(fleetSettings().createConcurrency).toBe(1);
});
it.each(['0', '-1', '1.5', 'invalid', 'Infinity', '9007199254740992'])('rejects invalid limit %s', async value => {
vi.stubEnv('DEPLOYMENT_CREATE_CONCURRENCY', value);
await expect(import('../../src/lib/server/config.js')).rejects.toThrow(/DEPLOYMENT_CREATE_CONCURRENCY/);
const { fleetSettings } = await import('../../src/lib/server/settings.js');
expect(() => fleetSettings()).toThrow(/Concurrent server startups/);
vi.stubEnv('DEPLOYMENT_CREATE_CONCURRENCY', '1');
});
});