Automatically place servers using learned resource capacity
This commit is contained in:
@@ -4,6 +4,7 @@ import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypt
|
||||
import { isIP } from 'node:net';
|
||||
import { db } from '../db/index.js';
|
||||
import { ensureHostSchema } from './schema.js';
|
||||
import { recordInstanceResources, validateInstanceResources } from './calibration.js';
|
||||
import type { Host, HostDecommission, HostDeletion, HostInventory, HostMode, HostOperation, HostOperationState } from '../../hosts.js';
|
||||
|
||||
export const AGENT_PROTOCOL_VERSION = 1;
|
||||
@@ -219,6 +220,7 @@ export function validateInventory(value: unknown): HostInventory {
|
||||
if (inventory[key] !== undefined && (typeof inventory[key] !== 'number' || !Number.isSafeInteger(inventory[key]) || Number(inventory[key]) < 0)) throw new Error(`Invalid inventory ${key}.`);
|
||||
}
|
||||
if (inventory.cpuUsagePercent !== undefined && (typeof inventory.cpuUsagePercent !== 'number' || !Number.isFinite(inventory.cpuUsagePercent) || inventory.cpuUsagePercent < 0 || inventory.cpuUsagePercent > 100)) throw new Error('Invalid inventory cpuUsagePercent.');
|
||||
validateInstanceResources(inventory);
|
||||
for (const key of ['dockerRunning','directoriesWritable']) if (inventory[key] !== undefined && typeof inventory[key] !== 'boolean') throw new Error(`Invalid inventory ${key}.`);
|
||||
if (inventory.runtimeImages !== undefined && (!Array.isArray(inventory.runtimeImages) || inventory.runtimeImages.some(image => typeof image !== 'string'))) throw new Error('Invalid runtime image inventory.');
|
||||
if (inventory.capabilities !== undefined && (!Array.isArray(inventory.capabilities) || inventory.capabilities.length > 32 || inventory.capabilities.some(value => typeof value !== 'string' || value.length > 80))) throw new Error('Invalid agent capability inventory.');
|
||||
@@ -253,6 +255,7 @@ export function enrollHost(token: unknown, rawInventory: unknown): { hostId: str
|
||||
revokeCredentials(enrollment.host_id);
|
||||
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());
|
||||
return { hostId: enrollment.host_id, credential, heartbeatSeconds: 15 };
|
||||
})();
|
||||
}
|
||||
@@ -284,9 +287,23 @@ export function rotateCredential(hostId: string, nextCredential?: string): strin
|
||||
}
|
||||
export function heartbeat(hostId: string, value: unknown): Host {
|
||||
requireHost(hostId); const inventory = validateInventory(value);
|
||||
database().prepare('UPDATE ec_hosts SET last_heartbeat=?,hostname=?,inventory=? WHERE id=?').run(now(), inventory.hostname ?? '', JSON.stringify(inventory), hostId);
|
||||
database().transaction(() => {
|
||||
const timestamp = now();
|
||||
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);
|
||||
}
|
||||
|
||||
function recordResources(hostId: string, inventory: HostInventory, timestamp: string): void {
|
||||
recordInstanceResources(hostId, inventory, timestamp);
|
||||
database().prepare(`INSERT OR REPLACE INTO ec_host_resource_samples
|
||||
(host_id,sampled_at,cpu_count,cpu_usage_percent,memory_total,memory_available) VALUES(?,?,?,?,?,?)`)
|
||||
.run(hostId, timestamp, inventory.cpuCount ?? null, inventory.cpuUsagePercent ?? null,
|
||||
inventory.memoryTotal ?? null, inventory.memoryAvailable ?? null);
|
||||
database().prepare('DELETE FROM ec_host_resource_samples WHERE host_id=? AND sampled_at<?')
|
||||
.run(hostId, new Date(Date.parse(timestamp) - 7 * 86400_000).toISOString());
|
||||
}
|
||||
export function requireReadyHost(hostId: string, options: { libraryId?: string; requiredBytes?: number; image?: string; port?: number; bindAddress?: string; protocol?: 'udp' | 'tcp' } = {}): Host {
|
||||
const host = requireHost(hostId); const reasons = [...host.readinessReasons];
|
||||
if (options.requiredBytes !== undefined && (host.inventory.storageAvailable ?? 0) < options.requiredBytes + host.minimumFreeBytes) reasons.push('Insufficient storage for this deployment.');
|
||||
|
||||
Reference in New Issue
Block a user