Fix native provisioning scheduling and progress reporting
This commit is contained in:
@@ -19,7 +19,13 @@ const request: GroupDefinition = { key: 'request', label: 'Request and eligibili
|
||||
const creation: GroupDefinition = { key: 'create', label: 'Create and start', phases: ['startup-queue', 'prepare', 'create-deployment', 'container-action'] };
|
||||
const removal: GroupDefinition = { key: 'remove', label: 'Remove the instance', phases: ['remove-container', 'delete-deployment', 'cleanup'] };
|
||||
const readiness: GroupDefinition = { key: 'verify', label: 'Verify readiness', phases: ['verify-container', 'verify-game', 'verify-map', 'verify-plutonium'] };
|
||||
function definitions(action: ProvisioningAction): GroupDefinition[] {
|
||||
function definitions(action: ProvisioningAction, native = false): GroupDefinition[] {
|
||||
if (native) {
|
||||
const command: GroupDefinition = { key: 'command', label: `${actionLabels[action]} on host agent`, phases: ['container-action'] };
|
||||
if (action === 'create' || action === 'rebuild') return [request,
|
||||
{ key: 'create', label: action === 'rebuild' ? 'Rebuild on host agent' : 'Create and start on host agent', phases: ['startup-queue', 'prepare', 'container-action'] }, readiness];
|
||||
if (action === 'destroy') return [request, command, { key: 'cleanup', label: 'Retire instance', phases: ['cleanup'] }];
|
||||
}
|
||||
if (action === 'create') return [{ ...request, phases: ['requested'] }, creation, readiness];
|
||||
if (action === 'rebuild') return [request, removal, { ...creation, label: 'Create a fresh instance' }, readiness];
|
||||
if (action === 'destroy') return [request, removal];
|
||||
@@ -30,7 +36,7 @@ function definitions(action: ProvisioningAction): GroupDefinition[] {
|
||||
export function provisioningGroups(detail: ProvisioningDetail): ProgressGroup[] {
|
||||
const progress = detail.progress;
|
||||
if (!progress) return [];
|
||||
const groups = definitions(detail.deployment.action);
|
||||
const groups = definitions(detail.deployment.action, detail.backend === 'native');
|
||||
const phases = groups.flatMap(group => group.phases);
|
||||
const current = progress.phase ? phases.indexOf(progress.phase) : -1;
|
||||
const active = activeProvisioningStates.includes(detail.deployment.state);
|
||||
@@ -48,7 +54,10 @@ export function provisioningGroups(detail: ProvisioningDetail): ProgressGroup[]
|
||||
: isCurrent && active ? progress.blocker || ['waiting', 'queued'].includes(detail.deployment.state) ? 'waiting' : 'current'
|
||||
: current >= 0 && index >= current && detail.deployment.state === 'cancelled' ? 'cancelled'
|
||||
: active && current >= 0 && index > current ? 'pending' : 'unknown';
|
||||
const label = key === 'container-action' && ['start', 'restart', 'stop'].includes(detail.deployment.action)
|
||||
const label = detail.backend === 'native' && key === 'startup-queue' ? 'Admit to startup capacity'
|
||||
: detail.backend === 'native' && key === 'prepare' ? 'Prepare launch payload'
|
||||
: detail.backend === 'native' && key === 'container-action' ? `${actionLabels[detail.deployment.action]} instance through host agent`
|
||||
: key === 'container-action' && ['start', 'restart', 'stop'].includes(detail.deployment.action)
|
||||
? `${actionLabels[detail.deployment.action]} existing container` : phaseLabels[key];
|
||||
return { key, label, state, at: check?.checkedAt ?? checkpoint?.at ?? (isCurrent ? progress.phaseStartedAt : null) };
|
||||
});
|
||||
@@ -93,7 +102,7 @@ export function provisioningOutcome(detail: ProvisioningDetail) {
|
||||
else if (progress?.blocker) description = progress.blocker.automatic
|
||||
? 'The worker will recheck this condition and continue automatically when eligible.'
|
||||
: 'Review the blocking condition below before this operation can continue.';
|
||||
else if (operation.state === 'queued') description = 'The request is saved. It will start when its dependencies and startup capacity allow.';
|
||||
else if (operation.state === 'queued') description = 'The request is saved and awaiting provisioning evaluation. No capacity blocker has been recorded.';
|
||||
else if (operation.state === 'waiting') description = 'Waiting for the worker’s eligibility checks. The recorded stage below describes the last known condition.';
|
||||
else description = operation.action === 'destroy' ? 'Removal is in progress. Confirmed checkpoints and remaining cleanup are recorded below.'
|
||||
: operation.action === 'rebuild' ? 'The old instance is removed before a fresh instance is created in its reserved slot. Follow each checkpoint below.'
|
||||
@@ -151,6 +160,12 @@ export function isProvisioningEventPage(value: unknown): value is ProvisioningEv
|
||||
/** Validate every nested shape consumed by the page before replacing its last good snapshot. */
|
||||
export function isProvisioningDetail(value: unknown, id: string): value is ProvisioningDetail {
|
||||
if (!record(value) || !record(value.deployment)) return false;
|
||||
if (value.backend !== undefined && !['native', 'komodo'].includes(value.backend as string)) return false;
|
||||
if (value.workerActivity !== undefined && (!Array.isArray(value.workerActivity) || !value.workerActivity.every(item =>
|
||||
record(item) && text(item.key) && text(item.label) && timestamp(item.startedAt)))) return false;
|
||||
if (value.agentOperation !== undefined && value.agentOperation !== null && !(record(value.agentOperation)
|
||||
&& text(value.agentOperation.id) && text(value.agentOperation.hostId) && text(value.agentOperation.state)
|
||||
&& timestamp(value.agentOperation.updatedAt) && timestamp(value.agentOperation.createdAt) && nullableText(value.agentOperation.error))) return false;
|
||||
const operation = value.deployment;
|
||||
if (operation.id !== id || !provisioningActions.includes(operation.action as ProvisioningAction)
|
||||
|| !['server', 'state', 'stage', 'triggered_by', 'warnings'].every(key => text(operation[key]))
|
||||
@@ -189,7 +204,7 @@ export function isProvisioningDetail(value: unknown, id: string): value is Provi
|
||||
if (!Array.isArray(progress.checks) || !progress.checks.every(check => record(check)
|
||||
&& ['container', 'game', 'map', 'plutonium-release', 'plutonium-version', 'occupancy', 'stopped'].includes(check.key as string)
|
||||
&& ['pass', 'waiting', 'fail', 'unknown'].includes(check.result as string)
|
||||
&& [null, 'komodo', 'rcon', 'telemetry', 'combined', 'manifest'].includes(check.source as null | string)
|
||||
&& [null, 'komodo', 'agent', 'rcon', 'telemetry', 'combined', 'manifest'].includes(check.source as null | string)
|
||||
&& text(check.label) && timestamp(check.checkedAt) && nullableTime(check.receivedAt)
|
||||
&& scalar(check.expected) && scalar(check.actual) && nullableText(check.message))) return false;
|
||||
if (!Array.isArray(progress.checkpoints) || !progress.checkpoints.every(checkpoint => record(checkpoint)
|
||||
|
||||
Reference in New Issue
Block a user