Save autoscaling policies without a preview step
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { db } from '../db/index.js';
|
||||
import { liveScaling, scalingSettings, type ScalingPolicy, type ScalingPreview, type ScalingReport, type ScalingView } from '../../autoscaling.js';
|
||||
import { liveScaling, scalingSettings, type ScalingPolicy, type ScalingEvaluation, type ScalingReport, type ScalingView } from '../../autoscaling.js';
|
||||
import { emptyObservation, planScaling } from './planner.js';
|
||||
import { parseServerName } from '../../map-aliases.js';
|
||||
import { scalingSnapshot } from './snapshot.js';
|
||||
@@ -21,12 +21,12 @@ export const listScalingPolicies = (): ScalingPolicy[] => (db().prepare('SELECT
|
||||
export function getScalingPolicy(id: string): ScalingPolicy | undefined { const row = db().prepare('SELECT * FROM autoscaling_policies WHERE id=?').get(id) as PolicyRow | undefined; return row && present(row); }
|
||||
export const scalingGloballyPaused = (): boolean => (db().prepare("SELECT value FROM settings WHERE key='autoscaling_paused'").get() as { value: string } | undefined)?.value === '1';
|
||||
|
||||
export function previewScaling(input: unknown, id?: string, revision?: number): ScalingPreview {
|
||||
export function evaluateScaling(input: unknown, id?: string, revision?: number): ScalingEvaluation {
|
||||
const settings = scalingSettings(input), existing = id ? getScalingPolicy(id) : undefined;
|
||||
if (id && !existing) throw new ScalingError('Policy not found.', 404);
|
||||
if (existing && existing.revision !== revision) throw new ScalingError('Policy changed. Refresh before editing.', 409);
|
||||
if (existing && (existing.game !== settings.game || existing.mapSlug !== settings.mapSlug)) throw new ScalingError('A policy cannot change its game or map.');
|
||||
const policy: ScalingPolicy = { ...settings, id: existing?.id ?? 'preview', revision: existing?.revision ?? 0,
|
||||
const policy: ScalingPolicy = { ...settings, id: existing?.id ?? 'new-policy', revision: existing?.revision ?? 0,
|
||||
pausedReason: null, updatedAt: existing?.updatedAt ?? '' };
|
||||
const snapshot = scalingSnapshot(policy);
|
||||
const blockers: string[] = [];
|
||||
@@ -43,17 +43,14 @@ export function previewScaling(input: unknown, id?: string, revision?: number):
|
||||
try { nativeLaunchSelection({ automatic: true }, policy.game); } catch (error) { blockers.push((error as Error).message); }
|
||||
}
|
||||
const decision = planScaling(policy, snapshot, Date.now());
|
||||
const token = createHash('sha256').update(JSON.stringify({ settings, revision: policy.revision, globalPaused: scalingGloballyPaused(),
|
||||
members: snapshot.members.map(m => [m.name, m.identity, m.state]), keyPool: snapshot.keyPool, reservations: snapshot.reservedNames.sort(), blockers: snapshot.blockers, decision })).digest('hex');
|
||||
return { policy, snapshot, decision, token, blockers };
|
||||
return { policy, snapshot, decision, blockers };
|
||||
}
|
||||
|
||||
export function saveScaling(input: unknown, id?: string, revision?: number, token?: string): ScalingPolicy {
|
||||
export function saveScaling(input: unknown, id?: string, revision?: number): ScalingPolicy {
|
||||
return db().transaction(() => {
|
||||
const preview = previewScaling(input, id, revision);
|
||||
if (!token || preview.token !== token) throw new ScalingError('The policy preview changed. Review it again before saving.', 409);
|
||||
if (preview.blockers.length) throw new ScalingError(preview.blockers[0]);
|
||||
const policy = preview.policy, settings = scalingSettings(policy), nextId = id ?? randomUUID(), at = new Date().toISOString();
|
||||
const evaluation = evaluateScaling(input, id, revision);
|
||||
if (evaluation.blockers.length) throw new ScalingError(evaluation.blockers[0]);
|
||||
const policy = evaluation.policy, settings = scalingSettings(policy), nextId = id ?? randomUUID(), at = new Date().toISOString();
|
||||
if (id) revokePolicyRemovals(id);
|
||||
if (!id && db().prepare('SELECT 1 FROM autoscaling_policies WHERE game=? AND map_slug=?').get(policy.game, policy.mapSlug)) throw new ScalingError('A policy already exists for this scope.', 409);
|
||||
db().prepare(`INSERT INTO autoscaling_policies(id,game,map_slug,settings,mode,updated_at) VALUES(?,?,?,?,?,?)
|
||||
@@ -61,7 +58,7 @@ export function saveScaling(input: unknown, id?: string, revision?: number, toke
|
||||
// An edit starts a new observation interval; earlier inactivity is not authorization.
|
||||
db().prepare('DELETE FROM autoscaling_members WHERE policy_id=?').run(nextId);
|
||||
db().prepare('UPDATE autoscaling_policies SET owns_map=CASE WHEN ? THEN 1 ELSE owns_map END WHERE id=?').run(liveScaling(policy.mode) ? 1 : 0, nextId);
|
||||
if (liveScaling(policy.mode)) for (const member of preview.snapshot.members) db().prepare('INSERT INTO autoscaling_members(policy_id,server,identity,observation,adopted) VALUES(?,?,?,?,1)')
|
||||
if (liveScaling(policy.mode)) for (const member of evaluation.snapshot.members) db().prepare('INSERT INTO autoscaling_members(policy_id,server,identity,observation,adopted) VALUES(?,?,?,?,1)')
|
||||
.run(nextId, member.name, member.identity, JSON.stringify({ identity: member.identity, observedAt: member.observedAt, emptySince: null, evaluatedAt: Date.now() }));
|
||||
reconcileScaling(getScalingPolicy(nextId)!);
|
||||
return getScalingPolicy(nextId)!;
|
||||
@@ -213,7 +210,7 @@ export function scalingReport(): ScalingReport {
|
||||
MAX(CASE WHEN d.action='create' AND d.state IN ('done','warning') THEN (julianday(d.finished_at)-julianday(d.started_at))*86400 END) AS readinessMaxSeconds
|
||||
FROM server_deployments d JOIN provisioning_sources s ON s.operation_id=d.id
|
||||
WHERE s.kind='autoscaling' AND s.source_id=? AND d.started_at>=?`).get(policy.id, new Date(Date.now() - 31 * 86400_000).toISOString()) as ScalingView['operations'];
|
||||
return { policy, snapshot, decision, token: '', blockers: [], history, lastEvaluatedAt: history[0]?.lastSeenAt ?? null, totals, operations, unsettled, ownsMap };
|
||||
return { policy, snapshot, decision, blockers: [], history, lastEvaluatedAt: history[0]?.lastSeenAt ?? null, totals, operations, unsettled, ownsMap };
|
||||
});
|
||||
return { policies, globalPaused: scalingGloballyPaused(), liveAvailable: true, removalAvailable: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user