Return queued lifecycle jobs before waking provisioning
This commit is contained in:
@@ -2,7 +2,7 @@ import { json } from '@sveltejs/kit';
|
|||||||
import { deploymentListPage } from '$lib/server/provisioning/history.js';
|
import { deploymentListPage } from '$lib/server/provisioning/history.js';
|
||||||
import { requestLifecycle } from '$lib/server/provisioning/index.js';
|
import { requestLifecycle } from '$lib/server/provisioning/index.js';
|
||||||
import { provisioningActions, runtimeActions, type ProvisioningAction, type RuntimeAction } from '$lib/provisioning.js';
|
import { provisioningActions, runtimeActions, type ProvisioningAction, type RuntimeAction } from '$lib/provisioning.js';
|
||||||
import { kick } from '$lib/server/pipeline/worker.js';
|
import { kickProvisioning } from '$lib/server/pipeline/worker.js';
|
||||||
|
|
||||||
export function GET({ url }: { url: URL }) {
|
export function GET({ url }: { url: URL }) {
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
@@ -25,7 +25,9 @@ export async function POST({ request }: { request: Request }) {
|
|||||||
try {
|
try {
|
||||||
const result = requestLifecycle(body.server, body.action as RuntimeAction);
|
const result = requestLifecycle(body.server, body.action as RuntimeAction);
|
||||||
if (!result.ok) return json({ error: result.error }, { status: result.status });
|
if (!result.ok) return json({ error: result.error }, { status: result.status });
|
||||||
void kick().catch(err => console.error('[provisioning] worker kick failed:', err));
|
setImmediate(() => {
|
||||||
|
void kickProvisioning().catch(err => console.error('[provisioning] worker kick failed:', err));
|
||||||
|
});
|
||||||
return json(result, { status: 202 });
|
return json(result, { status: 202 });
|
||||||
} catch (err) { return json({ error: (err as Error).message }, { status: 400 }); }
|
} catch (err) { return json({ error: (err as Error).message }, { status: 400 }); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fleetSnapshot } from '$lib/server/fleet/snapshot.js';
|
import { fleetSnapshot } from '$lib/server/fleet/snapshot.js';
|
||||||
import { kick } from '$lib/server/pipeline/worker.js';
|
import { kickProvisioning } from '$lib/server/pipeline/worker.js';
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { queueCreateServer } from '$lib/server/provisioning/index.js';
|
import { queueCreateServer } from '$lib/server/provisioning/index.js';
|
||||||
import type { GameMode, ZombieGame } from '$lib/server/fleet/map-aliases.js';
|
import type { GameMode, ZombieGame } from '$lib/server/fleet/map-aliases.js';
|
||||||
@@ -37,6 +37,9 @@ export async function POST({ request }: { request: Request }) {
|
|||||||
: 500;
|
: 500;
|
||||||
return json({ ok: false, error: result.message, kind: result.kind }, { status });
|
return json({ ok: false, error: result.message, kind: result.kind }, { status });
|
||||||
}
|
}
|
||||||
void kick().catch((err) => console.error('[create] worker kick failed:', err));
|
// Return the durable job ID before the worker's synchronous admission work.
|
||||||
|
setImmediate(() => {
|
||||||
|
void kickProvisioning().catch((err) => console.error('[create] worker kick failed:', err));
|
||||||
|
});
|
||||||
return json({ ok: true, name: result.name, port: result.port, deploymentId: result.deploymentId }, { status: 202 });
|
return json({ ok: true, name: result.name, port: result.port, deploymentId: result.deploymentId }, { status: 202 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { kick } from '$lib/server/pipeline/worker.js';
|
import { kickProvisioning } from '$lib/server/pipeline/worker.js';
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { destroyServer } from '$lib/server/provisioning/index.js';
|
import { destroyServer } from '$lib/server/provisioning/index.js';
|
||||||
import { serverSnapshot } from '$lib/server/fleet/snapshot.js';
|
import { serverSnapshot } from '$lib/server/fleet/snapshot.js';
|
||||||
@@ -17,6 +17,8 @@ export async function DELETE({ params }: { params: { name: string } }) {
|
|||||||
const status = result.kind === 'not-found' ? 404 : result.kind === 'conflict' ? 409 : 500;
|
const status = result.kind === 'not-found' ? 404 : result.kind === 'conflict' ? 409 : 500;
|
||||||
return json({ ok: false, error: result.message, kind: result.kind, warnings: result.warnings }, { status });
|
return json({ ok: false, error: result.message, kind: result.kind, warnings: result.warnings }, { status });
|
||||||
}
|
}
|
||||||
void kick().catch((err) => console.error('[destroy] worker kick failed:', err));
|
setImmediate(() => {
|
||||||
|
void kickProvisioning().catch((err) => console.error('[destroy] worker kick failed:', err));
|
||||||
|
});
|
||||||
return json({ ok: true, deploymentId: result.deploymentId }, { status: 202 });
|
return json({ ok: true, deploymentId: result.deploymentId }, { status: 202 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||||
|
import { queueCreateServer, destroyServer, requestLifecycle } from '../../src/lib/server/provisioning/index.js';
|
||||||
|
import { kick, kickProvisioning } from '../../src/lib/server/pipeline/worker.js';
|
||||||
|
import { POST as create } from '../../src/routes/api/servers/+server.js';
|
||||||
|
import { DELETE as destroy } from '../../src/routes/api/servers/[name]/+server.js';
|
||||||
|
import { POST as lifecycle } from '../../src/routes/api/provisioning/+server.js';
|
||||||
|
|
||||||
|
vi.mock('../../src/lib/server/provisioning/index.js', () => ({
|
||||||
|
queueCreateServer: vi.fn(), destroyServer: vi.fn(), requestLifecycle: vi.fn()
|
||||||
|
}));
|
||||||
|
vi.mock('../../src/lib/server/pipeline/worker.js', () => ({
|
||||||
|
kick: vi.fn(async () => {}), kickProvisioning: vi.fn(async () => {})
|
||||||
|
}));
|
||||||
|
vi.mock('../../src/lib/server/fleet/snapshot.js', () => ({ fleetSnapshot: vi.fn(), serverSnapshot: vi.fn() }));
|
||||||
|
vi.mock('../../src/lib/server/provisioning/history.js', () => ({ deploymentListPage: vi.fn() }));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useFakeTimers({ toFake: ['setImmediate'] });
|
||||||
|
});
|
||||||
|
afterEach(() => vi.useRealTimers());
|
||||||
|
|
||||||
|
const request = (body: unknown) => new Request('http://localhost/api/servers', {
|
||||||
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
const routes = [
|
||||||
|
{ name: 'create', queue: queueCreateServer, invoke: () => create({ request: request({ game: 't5', gameMode: 'zombie', mapSlug: 'theater' }) }) },
|
||||||
|
{ name: 'destroy', queue: destroyServer, invoke: () => destroy({ params: { name: 'server' } }) },
|
||||||
|
{ name: 'restart', queue: requestLifecycle, invoke: () => lifecycle({ request: request({ server: 'server', action: 'restart' }) }) }
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(routes)('returns the queued $name job before starting provisioning or unrelated observers', async ({ queue, invoke }) => {
|
||||||
|
vi.mocked(queue).mockReturnValue({ ok: true, name: 'server', port: 28960, deploymentId: 'job', warnings: [] });
|
||||||
|
const response = await invoke();
|
||||||
|
expect(response.status).toBe(202);
|
||||||
|
expect(await response.json()).toMatchObject({ ok: true, deploymentId: 'job' });
|
||||||
|
expect(queue).toHaveBeenCalledTimes(1);
|
||||||
|
expect(kickProvisioning).not.toHaveBeenCalled();
|
||||||
|
expect(kick).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
expect(kickProvisioning).toHaveBeenCalledTimes(1);
|
||||||
|
expect(kick).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(routes)('does not wake workers when $name admission fails', async ({ queue, invoke }) => {
|
||||||
|
vi.mocked(queue).mockReturnValue({ ok: false, kind: 'conflict', message: 'Busy', error: 'Busy', status: 409, warnings: [] });
|
||||||
|
const response = await invoke();
|
||||||
|
expect(response.status).toBe(409);
|
||||||
|
expect(await response.json()).toMatchObject({ error: 'Busy' });
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
expect(kickProvisioning).not.toHaveBeenCalled();
|
||||||
|
expect(kick).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user