Queue native collection launches before async placement
This commit is contained in:
@@ -5,6 +5,7 @@ import { isIP } from 'node:net';
|
||||
import { db } from '../db/index.js';
|
||||
import { ensureHostSchema } from './schema.js';
|
||||
import { recordInstanceResources, validateInstanceResources } from './calibration.js';
|
||||
import { refreshHostAvailability, scheduleAvailabilityRefresh } from './availability.js';
|
||||
import type { Host, HostDecommission, HostDeletion, HostInventory, HostMode, HostOperation, HostOperationState } from '../../hosts.js';
|
||||
|
||||
export const AGENT_PROTOCOL_VERSION = 1;
|
||||
@@ -83,6 +84,17 @@ export function hostDeletionStatus(id: string): HostDeletion {
|
||||
const names = instances.slice(0, 5).map(instance => instance.server).join(', ') + (instances.length > 5 ? ', …' : '');
|
||||
blockers.push(`Remove the ${instances.length} server instance${instances.length === 1 ? '' : 's'} assigned to this host first (${names}). Queued and failed deployments also require removal.`);
|
||||
}
|
||||
const pinned = handle.prepare(`SELECT d.server FROM server_deployments d
|
||||
JOIN native_creation_intents q ON q.deployment_id=d.id
|
||||
LEFT JOIN native_provisioning n ON n.deployment_id=d.id
|
||||
WHERE d.action='create' AND d.server_deleted_at IS NULL
|
||||
AND d.state IN ('queued','waiting','running','awaiting-health','interrupted') AND n.deployment_id IS NULL
|
||||
AND json_extract(q.selection,'$.hostId')=? AND COALESCE(json_extract(q.selection,'$.automatic'),0)!=1
|
||||
ORDER BY d.server LIMIT 6`).all(id) as { server: string }[];
|
||||
if (pinned.length) {
|
||||
const names = pinned.slice(0, 5).map(job => job.server).join(', ') + (pinned.length > 5 ? ', …' : '');
|
||||
blockers.push(`Cancel the queued server launches pinned to this host before deleting it (${names}).`);
|
||||
}
|
||||
const ports = handle.prepare('SELECT COUNT(*) AS count FROM ec_host_ports WHERE host_id=?').get(id) as { count: number };
|
||||
if (ports.count && !instances.length) blockers.push(`This host still has ${ports.count} reserved game port${ports.count === 1 ? '' : 's'}. Finish or reconcile the corresponding server removals first.`);
|
||||
// A pending command may have reached the agent before its acknowledgement.
|
||||
@@ -174,6 +186,7 @@ export function createHost(input: Record<string, unknown>): Host {
|
||||
const bind = input.bindAddress === undefined ? '0.0.0.0' : textField(input.bindAddress, 'Bind address');
|
||||
if (!isIP(bind)) throw new Error('Bind address must be an IPv4 or IPv6 address.');
|
||||
database().prepare('INSERT INTO ec_hosts(id,name,port_min,port_max,bind_address,created_at) VALUES(?,?,?,?,?,?)').run(id, name, min, max, bind, now());
|
||||
scheduleAvailabilityRefresh();
|
||||
return requireHost(id);
|
||||
}
|
||||
export function updateHost(id: string, input: Record<string, unknown>): Host {
|
||||
@@ -190,6 +203,7 @@ export function updateHost(id: string, input: Record<string, unknown>): Host {
|
||||
const minimum = input.minimumFreeBytes ?? host.minimumFreeBytes;
|
||||
if (typeof minimum !== 'number' || !Number.isSafeInteger(minimum) || minimum < 0) throw new Error('Minimum free storage must be a nonnegative byte count.');
|
||||
database().prepare('UPDATE ec_hosts SET name=?,mode=?,port_min=?,port_max=?,bind_address=?,public_address=?,minimum_free_bytes=? WHERE id=?').run(name, mode, min, max, bind, publicAddress, minimum, id);
|
||||
scheduleAvailabilityRefresh();
|
||||
return requireHost(id);
|
||||
}
|
||||
|
||||
@@ -256,6 +270,7 @@ export function enrollHost(token: unknown, rawInventory: unknown): { hostId: str
|
||||
const credential = issueCredential(enrollment.host_id);
|
||||
database().prepare('UPDATE ec_hosts SET enrolled_at=?,last_heartbeat=?,hostname=?,inventory=? WHERE id=?').run(now(), now(), inventory.hostname ?? '', JSON.stringify(inventory), enrollment.host_id);
|
||||
recordResources(enrollment.host_id, inventory, now());
|
||||
scheduleAvailabilityRefresh();
|
||||
return { hostId: enrollment.host_id, credential, heartbeatSeconds: 15 };
|
||||
})();
|
||||
}
|
||||
@@ -270,6 +285,7 @@ export function revokeCredentials(hostId: string): void {
|
||||
const host = requireHost(hostId);
|
||||
if (host.decommission && !host.decommission.retryable) throw new Error('Host cleanup needs its credentials until removal is confirmed.');
|
||||
database().prepare('UPDATE ec_host_credentials SET revoked_at=? WHERE host_id=? AND revoked_at IS NULL').run(now(), hostId);
|
||||
scheduleAvailabilityRefresh();
|
||||
}
|
||||
export function rotateCredential(hostId: string, nextCredential?: string): string {
|
||||
requireHost(hostId);
|
||||
@@ -292,7 +308,9 @@ export function heartbeat(hostId: string, value: unknown): Host {
|
||||
database().prepare('UPDATE ec_hosts SET last_heartbeat=?,hostname=?,inventory=? WHERE id=?').run(timestamp, inventory.hostname ?? '', JSON.stringify(inventory), hostId);
|
||||
recordResources(hostId, inventory, timestamp);
|
||||
})();
|
||||
return requireHost(hostId);
|
||||
const host = requireHost(hostId);
|
||||
refreshHostAvailability(host);
|
||||
return host;
|
||||
}
|
||||
|
||||
function recordResources(hostId: string, inventory: HostInventory, timestamp: string): void {
|
||||
@@ -504,7 +522,11 @@ export function reserveHostPort(hostId: string, instanceId: string, options: { p
|
||||
const allocated = options.port ?? nextHostPort(hostId, [], bind, protocol);
|
||||
if (allocated === null || !hostPortAvailable(hostId, allocated, bind, protocol, instanceId)) throw new Error('No available game port on the selected host.');
|
||||
database().prepare('INSERT INTO ec_host_ports(host_id,instance_id,bind_address,protocol,port,created_at) VALUES(?,?,?,?,?,?)').run(hostId, instanceId, bind, protocol, allocated, now());
|
||||
scheduleAvailabilityRefresh();
|
||||
return allocated;
|
||||
})();
|
||||
}
|
||||
export function releaseHostPorts(instanceId: string): void { database().prepare('DELETE FROM ec_host_ports WHERE instance_id=?').run(instanceId); }
|
||||
export function releaseHostPorts(instanceId: string): void {
|
||||
database().prepare('DELETE FROM ec_host_ports WHERE instance_id=?').run(instanceId);
|
||||
scheduleAvailabilityRefresh();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user