Add cancellation and cleanup for native server creation

This commit is contained in:
2026-10-04 18:00:11 -04:00
parent 19f1f4e279
commit 176b7dd86b
17 changed files with 482 additions and 26 deletions
+7 -9
View File
@@ -21,6 +21,7 @@ import { sha256Buffer } from '../fleet/files.js';
import type { StagedFileChange } from '../fleet/staged-files.js';
import type { LaunchPreview } from '../../deployment-plan.js';
import type { ProvisioningAction } from '../../provisioning.js';
import { cancelCreation, finishCreationCancellation } from './cancel-creation.js';
export interface NativeSelection { hostId: string; libraryId: string; image?: string; bindAddress?: string; publicAddress?: string; publicPort?: number }
type NativeRecipe = LaunchRecipe & { native?: NativeSelection };
@@ -146,15 +147,7 @@ export function cancelQueuedNativeCreation(server: string, triggeredBy: string):
const creation = db().prepare(`SELECT d.id FROM server_deployments d JOIN native_provisioning n ON n.deployment_id=d.id
WHERE n.instance_id=? AND d.action='create' AND d.state='queued' AND n.operation_id IS NULL`).get(instance.id) as { id: string } | undefined;
if (!creation) return null;
const id = beginDeployment(server, 'destroy', triggeredBy);
const now = new Date().toISOString();
db().prepare("UPDATE server_deployments SET state='cancelled',finished_at=?,stage='Queued native creation cancelled',server_deleted_at=? WHERE id=?")
.run(now, now, creation.id);
db().prepare('UPDATE native_instances SET retired_at=? WHERE id=?').run(now, instance.id);
releaseHostPorts(instance.id);
deploymentStage(id, 'Cancelled queued creation; no agent mutation was submitted');
finishDeployment(id, { ok: true });
return id;
return cancelCreation(creation.id, triggeredBy);
})();
}
@@ -277,6 +270,7 @@ export async function processNativeProvisioning(eligibility: (operation: { id: s
const activeJobs = new Map<string, Promise<void>>();
async function processJob(job: Job, eligibility: (job: Job) => string | null) {
if (!stillActive(job)) return;
let instance = nativeInstance(job.server);
if (!instance || instance.id !== job.instance_id || (instance.generation !== job.generation && !(job.action === 'rebuild' && instance.generation === job.generation + 1))) {
finishDeployment(job.id, { ok: false, message: 'Original instance was retired or replaced; no command was sent.' }); return;
@@ -288,6 +282,9 @@ async function processJob(job: Job, eligibility: (job: Job) => string | null) {
}
if (job.operation_id) {
const operation = getHostOperation(job.operation_id);
if (operation?.kind === 'instance.cancel-create' && operation.state === 'pending' && !host.inventory.capabilities?.includes('instance.cancel-create')) {
hold(job, 'Update the host agent to support creation cancellation. Creation is cancelled; cleanup and port release await the updated agent.'); return;
}
if (!operation || operation.state === 'unknown') { hold(job, 'Agent result is unknown. Reconciling the original operation; no replacement or repeated command will be sent.'); return; }
if (operation.state === 'failed') {
// The agent reports failed only when no mutation began. Ambiguous
@@ -312,6 +309,7 @@ async function processJob(job: Job, eligibility: (job: Job) => string | null) {
db().prepare('DELETE FROM servers WHERE name=?').run(job.server);
recordProgress(job.id, { checkpoint: { key: 'cleanup', label: 'Instance retired and port reservations released' } });
complete(job);
finishCreationCancellation(job.id);
})();
return;
}