@ornncompute/cli 1.0.72 → 1.0.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -24
- package/package.json +4 -2
- package/src/admin-capacity-reservations.mjs +6 -13
- package/src/admin-command-handlers.mjs +2 -1
- package/src/admin-credits.mjs +1 -2
- package/src/admin-enrollment.mjs +22 -30
- package/src/admin-mutations.mjs +6 -56
- package/src/admin-node-diagnostics.mjs +3 -41
- package/src/admin-node-lifecycle.mjs +40 -160
- package/src/admin-node-sanitize.mjs +243 -0
- package/src/admin-shell.mjs +39 -32
- package/src/catalog-shell.mjs +6 -6
- package/src/evidence-commands.mjs +4 -6
- package/src/external-bucket-commands.mjs +76 -96
- package/src/interaction-runtime.mjs +5 -37
- package/src/kubernetes-commands.mjs +25 -183
- package/src/launch-mode-handlers.mjs +0 -1
- package/src/machine-reference.mjs +33 -56
- package/src/mount-commands.mjs +25 -131
- package/src/slurm-commands.mjs +13 -2
- package/src/storage-commands.mjs +2 -87
- package/src/vm-commands.mjs +48 -101
- package/vendor/api-client/commerce.js +743 -0
- package/vendor/capabilities/cli.js +7688 -7
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
- package/vendor/capabilities/capabilities/admin-enrollment.js +0 -321
- package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -235
- package/vendor/capabilities/capabilities/admin.js +0 -91
- package/vendor/capabilities/capabilities/cli.js +0 -156
- package/vendor/capabilities/capabilities/clusters.js +0 -248
- package/vendor/capabilities/capabilities/commerce-market.js +0 -405
- package/vendor/capabilities/capabilities/compute.js +0 -523
- package/vendor/capabilities/capabilities/credits.js +0 -78
- package/vendor/capabilities/capabilities/evidence.js +0 -145
- package/vendor/capabilities/capabilities/external-buckets.js +0 -191
- package/vendor/capabilities/capabilities/fleet.js +0 -306
- package/vendor/capabilities/capabilities/identity.js +0 -129
- package/vendor/capabilities/capabilities/listings.js +0 -398
- package/vendor/capabilities/capabilities/locality.js +0 -42
- package/vendor/capabilities/capabilities/market-shared.js +0 -46
- package/vendor/capabilities/capabilities/markets.js +0 -482
- package/vendor/capabilities/capabilities/mounts.js +0 -176
- package/vendor/capabilities/capabilities/native-listing.js +0 -99
- package/vendor/capabilities/capabilities/networks.js +0 -1264
- package/vendor/capabilities/capabilities/observability.js +0 -218
- package/vendor/capabilities/capabilities/organization-invites.js +0 -53
- package/vendor/capabilities/capabilities/reservation-list.js +0 -68
- package/vendor/capabilities/capabilities/staff-shared.js +0 -64
- package/vendor/capabilities/capabilities/tokens.js +0 -165
- package/vendor/capabilities/capabilities/users.js +0 -159
- package/vendor/capabilities/capabilities/volumes.js +0 -178
- package/vendor/capabilities/capability.js +0 -99
- package/vendor/capabilities/cli-catalog.js +0 -73
- package/vendor/capabilities/cli-migrations.js +0 -264
- package/vendor/capabilities/names.js +0 -6
- package/vendor/capabilities/software-catalog.js +0 -82
- package/vendor/capabilities/spec-catalog.js +0 -151
- package/vendor/capabilities/supplier-directory.js +0 -20
|
@@ -36,11 +36,6 @@ function parseOptions(argv, allowed, usage) {
|
|
|
36
36
|
if (options[name] !== undefined) {
|
|
37
37
|
throw usageError(`${usage}\n--${name} may be supplied only once.`, name);
|
|
38
38
|
}
|
|
39
|
-
if (name === "yes") {
|
|
40
|
-
if (separator !== -1) throw usageError(`${usage}\n--yes does not take a value.`, "yes");
|
|
41
|
-
options.yes = true;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
39
|
const parsed = optionValue(argv, index, name, usage);
|
|
45
40
|
options[name] = parsed.value;
|
|
46
41
|
index += parsed.consumed;
|
|
@@ -64,18 +59,13 @@ function timeoutSeconds(value) {
|
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
export function parseAdminNodeDiagnoseArgs(argv) {
|
|
67
|
-
const { options, positionals } = parseOptions(
|
|
68
|
-
argv,
|
|
69
|
-
new Set(["yes", "idempotency-key"]),
|
|
70
|
-
DIAGNOSE_USAGE
|
|
71
|
-
);
|
|
62
|
+
const { options, positionals } = parseOptions(argv, new Set(["idempotency-key"]), DIAGNOSE_USAGE);
|
|
72
63
|
if (positionals.length !== 1 || !positionals[0]) throw usageError(DIAGNOSE_USAGE, "node-id");
|
|
73
64
|
return {
|
|
74
65
|
input: { node_id: positionals[0] },
|
|
75
66
|
mutation: {
|
|
76
67
|
idempotencyKey: options["idempotency-key"],
|
|
77
68
|
safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
|
|
78
|
-
yes: options.yes === true,
|
|
79
69
|
},
|
|
80
70
|
};
|
|
81
71
|
}
|
|
@@ -83,7 +73,7 @@ export function parseAdminNodeDiagnoseArgs(argv) {
|
|
|
83
73
|
export function parseAdminNodeExecArgs(argv) {
|
|
84
74
|
const { options, positionals } = parseOptions(
|
|
85
75
|
argv,
|
|
86
|
-
new Set(["command", "timeout", "
|
|
76
|
+
new Set(["command", "timeout", "idempotency-key"]),
|
|
87
77
|
EXEC_USAGE
|
|
88
78
|
);
|
|
89
79
|
if (positionals.length !== 1 || !positionals[0]) throw usageError(EXEC_USAGE, "node-id");
|
|
@@ -99,7 +89,6 @@ export function parseAdminNodeExecArgs(argv) {
|
|
|
99
89
|
mutation: {
|
|
100
90
|
idempotencyKey: options["idempotency-key"],
|
|
101
91
|
safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
|
|
102
|
-
yes: options.yes === true,
|
|
103
92
|
},
|
|
104
93
|
};
|
|
105
94
|
}
|
|
@@ -123,7 +112,6 @@ function nodeContext(payload, expectedNodeId) {
|
|
|
123
112
|
}
|
|
124
113
|
const explicitReachability =
|
|
125
114
|
presentString(payload.reachability?.status) ??
|
|
126
|
-
presentString(payload.off_grid_reachability?.status) ??
|
|
127
115
|
(typeof payload.reachable === "boolean"
|
|
128
116
|
? payload.reachable
|
|
129
117
|
? "reachable"
|
|
@@ -138,10 +126,7 @@ function nodeContext(payload, expectedNodeId) {
|
|
|
138
126
|
presentString(payload.status) ??
|
|
139
127
|
"unknown",
|
|
140
128
|
reachability: explicitReachability ?? "unknown",
|
|
141
|
-
observed_at:
|
|
142
|
-
presentString(payload.observed_at) ??
|
|
143
|
-
presentString(payload.last_heartbeat_at) ??
|
|
144
|
-
presentString(payload.off_grid_reachability?.last_checked_at),
|
|
129
|
+
observed_at: presentString(payload.observed_at) ?? presentString(payload.last_heartbeat_at),
|
|
145
130
|
};
|
|
146
131
|
}
|
|
147
132
|
|
|
@@ -272,27 +257,6 @@ function executionResult(raw, input, target) {
|
|
|
272
257
|
};
|
|
273
258
|
}
|
|
274
259
|
|
|
275
|
-
function declinedResult(input) {
|
|
276
|
-
return {
|
|
277
|
-
data: {
|
|
278
|
-
declined: true,
|
|
279
|
-
node: { node_id: input.node_id },
|
|
280
|
-
...(input.command ? { request: requestCorrelation(input) } : {}),
|
|
281
|
-
},
|
|
282
|
-
exitCode: 1,
|
|
283
|
-
outcome: { class: "failed", phase: "consent_declined" },
|
|
284
|
-
action: null,
|
|
285
|
-
resume: null,
|
|
286
|
-
errors: [
|
|
287
|
-
{
|
|
288
|
-
code: "consent_declined",
|
|
289
|
-
message: "The action was declined; no mutation was submitted.",
|
|
290
|
-
retryable: false,
|
|
291
|
-
},
|
|
292
|
-
],
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
260
|
function interruptedResult(input) {
|
|
297
261
|
return {
|
|
298
262
|
data: {
|
|
@@ -343,7 +307,6 @@ async function runDiagnosis({ input, mutation, runtime, execute, fetch }) {
|
|
|
343
307
|
},
|
|
344
308
|
execute: async () => execute({ node_id: input.node_id }, { confirmed: true }),
|
|
345
309
|
});
|
|
346
|
-
if (result.status === "declined") return declinedResult(input);
|
|
347
310
|
return diagnosisResult(result.data, target);
|
|
348
311
|
} catch (error) {
|
|
349
312
|
if (wasInterrupted(error, runtime)) return interruptedResult(input);
|
|
@@ -388,7 +351,6 @@ async function runExecution({ input, mutation, runtime, execute, fetch }) {
|
|
|
388
351
|
{ confirmed: true }
|
|
389
352
|
),
|
|
390
353
|
});
|
|
391
|
-
if (result.status === "declined") return declinedResult(input);
|
|
392
354
|
return executionResult(result.data, input, target);
|
|
393
355
|
} catch (error) {
|
|
394
356
|
if (wasInterrupted(error, runtime)) return interruptedResult(input);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getReservation } from "../vendor/api-client/commerce.js";
|
|
1
2
|
import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./admin-mutations.mjs";
|
|
2
3
|
import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
|
|
3
4
|
import { waitForGoal as waitForReadGoal } from "./wait-runtime.mjs";
|
|
@@ -9,9 +10,7 @@ const POLL_INTERVAL_MS = 1_000;
|
|
|
9
10
|
const COMMAND_BY_CAPABILITY = Object.freeze({
|
|
10
11
|
"nodes.reboot": "reboot",
|
|
11
12
|
"nodes.hard_reset": "hard-reset",
|
|
12
|
-
"
|
|
13
|
-
"nodes.resume_management": "resume-management",
|
|
14
|
-
"nodes.terminate": "terminate",
|
|
13
|
+
"node.terminate": "terminate",
|
|
15
14
|
"nodes.assign": "assign",
|
|
16
15
|
});
|
|
17
16
|
|
|
@@ -22,35 +21,22 @@ const CAPABILITY_BY_COMMAND = Object.freeze(
|
|
|
22
21
|
);
|
|
23
22
|
|
|
24
23
|
const USAGE = Object.freeze({
|
|
25
|
-
reboot: "Usage: ornn admin nodes reboot <node-ref> [--
|
|
24
|
+
reboot: "Usage: ornn admin nodes reboot <node-ref> [--no-wait] [--timeout SECONDS]",
|
|
26
25
|
"hard-reset":
|
|
27
|
-
"Usage: ornn admin nodes hard-reset <node-ref> --erase-local-data [--
|
|
28
|
-
"suspend-management":
|
|
29
|
-
"Usage: ornn admin nodes suspend-management <node-ref> [--reason TEXT] [--yes] [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
|
|
30
|
-
"resume-management":
|
|
31
|
-
"Usage: ornn admin nodes resume-management <node-ref> [--yes] [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
|
|
26
|
+
"Usage: ornn admin nodes hard-reset <node-ref> --erase-local-data [--no-wait] [--timeout SECONDS]",
|
|
32
27
|
terminate:
|
|
33
|
-
"Usage: ornn admin nodes terminate <node-ref> --delete-node-record [--
|
|
28
|
+
"Usage: ornn admin nodes terminate <node-ref> --delete-node-record [--reason TEXT] [--idempotency-key KEY]",
|
|
34
29
|
assign:
|
|
35
|
-
"Usage: ornn admin nodes assign <node-ref> --reservation RESERVATION [--
|
|
30
|
+
"Usage: ornn admin nodes assign <node-ref> --reservation RESERVATION [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
|
|
36
31
|
});
|
|
37
32
|
|
|
38
|
-
const BOOLEAN_OPTIONS = new Set([
|
|
39
|
-
"delete-node-record",
|
|
40
|
-
"erase-local-data",
|
|
41
|
-
"no-wait",
|
|
42
|
-
"skip-cleanup",
|
|
43
|
-
"wait",
|
|
44
|
-
"yes",
|
|
45
|
-
]);
|
|
33
|
+
const BOOLEAN_OPTIONS = new Set(["delete-node-record", "erase-local-data", "no-wait", "wait"]);
|
|
46
34
|
const VALUE_OPTIONS = new Set(["idempotency-key", "reason", "reservation", "timeout"]);
|
|
47
35
|
const ALLOWED_OPTIONS = Object.freeze({
|
|
48
|
-
reboot: new Set(["
|
|
49
|
-
"hard-reset": new Set(["erase-local-data", "
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
terminate: new Set(["delete-node-record", "skip-cleanup", "reason", "yes", "idempotency-key"]),
|
|
53
|
-
assign: new Set(["reservation", "yes", "idempotency-key", "wait", "no-wait", "timeout"]),
|
|
36
|
+
reboot: new Set(["wait", "no-wait", "timeout"]),
|
|
37
|
+
"hard-reset": new Set(["erase-local-data", "wait", "no-wait", "timeout"]),
|
|
38
|
+
terminate: new Set(["delete-node-record", "reason", "idempotency-key"]),
|
|
39
|
+
assign: new Set(["reservation", "idempotency-key", "wait", "no-wait", "timeout"]),
|
|
54
40
|
});
|
|
55
41
|
|
|
56
42
|
function mutationError(message, { code = "usage_error", field } = {}) {
|
|
@@ -135,22 +121,16 @@ export function parseAdminNodeLifecycleInput(command, argv) {
|
|
|
135
121
|
});
|
|
136
122
|
}
|
|
137
123
|
if (name === "hard-reset" && options["erase-local-data"] !== true) {
|
|
138
|
-
throw mutationError(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
field: "erase-local-data",
|
|
143
|
-
}
|
|
144
|
-
);
|
|
124
|
+
throw mutationError(`${usage}\nHard reset requires --erase-local-data.`, {
|
|
125
|
+
code: "erase_local_data_required",
|
|
126
|
+
field: "erase-local-data",
|
|
127
|
+
});
|
|
145
128
|
}
|
|
146
129
|
if (name === "terminate" && options["delete-node-record"] !== true) {
|
|
147
|
-
throw mutationError(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
field: "delete-node-record",
|
|
152
|
-
}
|
|
153
|
-
);
|
|
130
|
+
throw mutationError(`${usage}\nTermination requires --delete-node-record.`, {
|
|
131
|
+
code: "delete_node_record_required",
|
|
132
|
+
field: "delete-node-record",
|
|
133
|
+
});
|
|
154
134
|
}
|
|
155
135
|
if (name === "assign" && !options.reservation) {
|
|
156
136
|
throw mutationError(`${usage}\nAssignment requires --reservation.`, {
|
|
@@ -159,40 +139,29 @@ export function parseAdminNodeLifecycleInput(command, argv) {
|
|
|
159
139
|
});
|
|
160
140
|
}
|
|
161
141
|
if (name !== "hard-reset" && options["erase-local-data"]) throw mutationError(usage);
|
|
162
|
-
if (name !== "terminate" &&
|
|
163
|
-
throw mutationError(usage);
|
|
164
|
-
}
|
|
142
|
+
if (name !== "terminate" && options["delete-node-record"]) throw mutationError(usage);
|
|
165
143
|
if (name !== "assign" && options.reservation) throw mutationError(usage);
|
|
166
|
-
if (
|
|
167
|
-
throw mutationError(usage);
|
|
168
|
-
}
|
|
144
|
+
if (name !== "terminate" && options.reason) throw mutationError(usage);
|
|
169
145
|
|
|
170
146
|
const idempotencyKey = options["idempotency-key"]?.trim();
|
|
171
147
|
const safety =
|
|
172
148
|
name === "reboot" || name === "hard-reset"
|
|
173
|
-
? ADMIN_MUTATION_SAFETY.
|
|
149
|
+
? ADMIN_MUTATION_SAFETY.direct
|
|
174
150
|
: ADMIN_MUTATION_SAFETY.reconcile;
|
|
175
151
|
return {
|
|
176
152
|
input: {
|
|
177
153
|
node_ref: nodeRef,
|
|
178
|
-
...(options.yes ? { yes: true } : {}),
|
|
179
154
|
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
180
155
|
...(options.reason ? { reason: options.reason } : {}),
|
|
181
156
|
...(options.wait ? { wait: true } : options["no-wait"] ? { wait: false } : {}),
|
|
182
157
|
...(options.timeout ? { timeout_seconds: durationSeconds(options.timeout, usage) } : {}),
|
|
183
158
|
...(name === "hard-reset" ? { erase_local_data: true } : {}),
|
|
184
|
-
...(name === "terminate"
|
|
185
|
-
? {
|
|
186
|
-
delete_node_record: true,
|
|
187
|
-
...(options["skip-cleanup"] ? { skip_cleanup: true } : {}),
|
|
188
|
-
}
|
|
189
|
-
: {}),
|
|
159
|
+
...(name === "terminate" ? { delete_node_record: true } : {}),
|
|
190
160
|
...(name === "assign" ? { reservation_id: options.reservation } : {}),
|
|
191
161
|
},
|
|
192
162
|
mutation: {
|
|
193
163
|
idempotencyKey,
|
|
194
164
|
safety,
|
|
195
|
-
yes: options.yes === true,
|
|
196
165
|
},
|
|
197
166
|
};
|
|
198
167
|
}
|
|
@@ -218,16 +187,7 @@ function nodeSnapshot(node) {
|
|
|
218
187
|
organization_id: text(node?.organization_id),
|
|
219
188
|
facility_id: text(node?.facility_id ?? node?.facilityId),
|
|
220
189
|
reservation_id: reservationId(node),
|
|
221
|
-
off_grid: node?.off_grid === true,
|
|
222
190
|
cleanup_state: text(node?.cleanup_state),
|
|
223
|
-
reachability:
|
|
224
|
-
node?.reachable === false || node?.health_readiness?.reachable === false
|
|
225
|
-
? "unreachable"
|
|
226
|
-
: text(
|
|
227
|
-
node?.reachability?.status ??
|
|
228
|
-
node?.off_grid_reachability?.status ??
|
|
229
|
-
node?.health_readiness?.reachability
|
|
230
|
-
),
|
|
231
191
|
};
|
|
232
192
|
}
|
|
233
193
|
|
|
@@ -235,18 +195,6 @@ function sameSnapshot(left, right) {
|
|
|
235
195
|
return JSON.stringify(nodeSnapshot(left)) === JSON.stringify(nodeSnapshot(right));
|
|
236
196
|
}
|
|
237
197
|
|
|
238
|
-
function isOffGrid(node) {
|
|
239
|
-
const snapshot = nodeSnapshot(node);
|
|
240
|
-
return snapshot.off_grid || ["off-grid", "off_grid"].includes(snapshot.status.toLowerCase());
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function isUnreachable(node) {
|
|
244
|
-
const snapshot = nodeSnapshot(node);
|
|
245
|
-
return (
|
|
246
|
-
isOffGrid(node) || ["unreachable", "offline"].includes(snapshot.reachability.toLowerCase())
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
198
|
function nodeRows(payload) {
|
|
251
199
|
if (Array.isArray(payload)) return payload;
|
|
252
200
|
return Array.isArray(payload?.nodes) ? payload.nodes : [];
|
|
@@ -357,20 +305,18 @@ async function resolveLiveMachine(fetch, node) {
|
|
|
357
305
|
function reservationSnapshot(reservation, requestedId) {
|
|
358
306
|
return {
|
|
359
307
|
reservation_id: text(reservation?.reservationId) || requestedId,
|
|
360
|
-
organization_id: text(reservation?.organizationId),
|
|
308
|
+
organization_id: text(reservation?.customerId ?? reservation?.organizationId),
|
|
361
309
|
listing_id: text(reservation?.listingId),
|
|
362
310
|
kind: text(reservation?.kind),
|
|
363
311
|
status: text(reservation?.status),
|
|
364
|
-
node_count: Number(reservation?.
|
|
312
|
+
node_count: Number(reservation?.reservedNodeCount ?? 0) || 0,
|
|
365
313
|
starts_at: text(reservation?.startsAt),
|
|
366
314
|
ends_at: text(reservation?.endsAt),
|
|
367
315
|
};
|
|
368
316
|
}
|
|
369
317
|
|
|
370
318
|
async function readReservation(fetch, id) {
|
|
371
|
-
const reservation = await fetch
|
|
372
|
-
method: "GET",
|
|
373
|
-
});
|
|
319
|
+
const reservation = await getReservation(fetch, id);
|
|
374
320
|
return reservationSnapshot(reservation, id);
|
|
375
321
|
}
|
|
376
322
|
|
|
@@ -412,11 +358,9 @@ function safeOwnerResult(result) {
|
|
|
412
358
|
const safe = {};
|
|
413
359
|
for (const key of [
|
|
414
360
|
"deleted",
|
|
415
|
-
"forced",
|
|
416
361
|
"id",
|
|
417
362
|
"mode",
|
|
418
363
|
"node_id",
|
|
419
|
-
"off_grid",
|
|
420
364
|
"packet_id",
|
|
421
365
|
"request_id",
|
|
422
366
|
"reservation_id",
|
|
@@ -484,47 +428,13 @@ async function prepareLifecycle({ command, fetch, input, machine }) {
|
|
|
484
428
|
};
|
|
485
429
|
}
|
|
486
430
|
|
|
487
|
-
if (command === "suspend-management") {
|
|
488
|
-
fields.push({ label: "Host power", value: "leave powered on" });
|
|
489
|
-
fields.push({ label: "Managed access", value: "remove agent management and managed SSH keys" });
|
|
490
|
-
if (input.reason) fields.push({ label: "Reason", value: input.reason });
|
|
491
|
-
return {
|
|
492
|
-
review: {
|
|
493
|
-
title: "Review management suspension",
|
|
494
|
-
fields,
|
|
495
|
-
consequence: "Ornn management and managed access end while the host remains powered on.",
|
|
496
|
-
},
|
|
497
|
-
value,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (command === "resume-management") {
|
|
502
|
-
fields.push({ label: "Management", value: "remotely re-enroll the node" });
|
|
503
|
-
fields.push({ label: "Managed access", value: "restore after successful enrollment" });
|
|
504
|
-
return {
|
|
505
|
-
review: {
|
|
506
|
-
title: "Review management resumption",
|
|
507
|
-
fields,
|
|
508
|
-
consequence: "Ornn uses the stored admin access to reinstall management on this host.",
|
|
509
|
-
},
|
|
510
|
-
value,
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
|
|
514
431
|
if (command === "terminate") {
|
|
515
|
-
if (input.skip_cleanup && !isUnreachable(node)) {
|
|
516
|
-
throw mutationError(
|
|
517
|
-
"--skip-cleanup is allowed only when the authoritative node read says the node is unreachable or off-grid.",
|
|
518
|
-
{ code: "skip_cleanup_requires_unreachable", field: "skip-cleanup" }
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
432
|
fields.push({
|
|
522
433
|
label: "Host cleanup",
|
|
523
|
-
value:
|
|
524
|
-
|
|
525
|
-
: "not dispatched by termination; complete any required host wipe separately before reuse",
|
|
434
|
+
value:
|
|
435
|
+
"not dispatched by termination; complete any required host wipe separately before reuse",
|
|
526
436
|
});
|
|
527
|
-
fields.push({ label: "Node record", value: "
|
|
437
|
+
fields.push({ label: "Node record", value: "MARK TERMINATED" });
|
|
528
438
|
fields.push({
|
|
529
439
|
label: "Reservation/capacity consequence",
|
|
530
440
|
value: reservationId(node)
|
|
@@ -538,7 +448,7 @@ async function prepareLifecycle({ command, fetch, input, machine }) {
|
|
|
538
448
|
title: "Review node termination",
|
|
539
449
|
fields,
|
|
540
450
|
consequence:
|
|
541
|
-
"The node record is
|
|
451
|
+
"The node record is marked terminated; this does not cancel its capacity reservation.",
|
|
542
452
|
},
|
|
543
453
|
value,
|
|
544
454
|
};
|
|
@@ -582,11 +492,11 @@ async function reconcileLifecycle({ fetch, input, value }) {
|
|
|
582
492
|
data: { node_id: id, status: "already_absent", deleted: false },
|
|
583
493
|
};
|
|
584
494
|
}
|
|
585
|
-
|
|
495
|
+
// Terminate keeps the record, so a re-run finds it terminated, not gone.
|
|
496
|
+
if (isTerminated(current)) {
|
|
586
497
|
return {
|
|
587
|
-
status: "
|
|
588
|
-
|
|
589
|
-
"The node is no longer authoritatively unreachable/off-grid; renew review without --skip-cleanup.",
|
|
498
|
+
status: "satisfied",
|
|
499
|
+
data: { node_id: id, status: "terminated", deleted: true },
|
|
590
500
|
};
|
|
591
501
|
}
|
|
592
502
|
if (!sameSnapshot(reviewedNode, current)) {
|
|
@@ -599,29 +509,6 @@ async function reconcileLifecycle({ fetch, input, value }) {
|
|
|
599
509
|
}
|
|
600
510
|
if (!current) return { status: "conflict", message: "The exact node no longer exists." };
|
|
601
511
|
|
|
602
|
-
if (command === "suspend-management") {
|
|
603
|
-
if (isOffGrid(current)) {
|
|
604
|
-
return { status: "satisfied", data: { node_id: id, status: "off-grid", off_grid: true } };
|
|
605
|
-
}
|
|
606
|
-
if (!sameSnapshot(reviewedNode, current)) {
|
|
607
|
-
return { status: "conflict", message: "The node's management goal changed after review." };
|
|
608
|
-
}
|
|
609
|
-
return { status: "apply" };
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (command === "resume-management") {
|
|
613
|
-
if (!isOffGrid(current)) {
|
|
614
|
-
return {
|
|
615
|
-
status: "satisfied",
|
|
616
|
-
data: { node_id: id, status: nodeSnapshot(current).status, off_grid: false },
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
if (!sameSnapshot(reviewedNode, current)) {
|
|
620
|
-
return { status: "conflict", message: "The node's management goal changed after review." };
|
|
621
|
-
}
|
|
622
|
-
return { status: "apply" };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
512
|
if (command === "assign") {
|
|
626
513
|
const currentReservation = reservationId(current);
|
|
627
514
|
if (currentReservation === input.reservation_id) {
|
|
@@ -669,15 +556,17 @@ async function reconcileLifecycle({ fetch, input, value }) {
|
|
|
669
556
|
}
|
|
670
557
|
|
|
671
558
|
function goalSatisfied(command, node, input) {
|
|
672
|
-
if (command === "terminate") return node === null;
|
|
559
|
+
if (command === "terminate") return node === null || isTerminated(node);
|
|
673
560
|
if (!node) return false;
|
|
674
|
-
if (command === "suspend-management") return isOffGrid(node);
|
|
675
|
-
if (command === "resume-management") return !isOffGrid(node);
|
|
676
561
|
if (command === "assign") return reservationId(node) === input.reservation_id;
|
|
677
562
|
if (cleanupState(node) === "failed") return false;
|
|
678
563
|
return !restartInProgress(node);
|
|
679
564
|
}
|
|
680
565
|
|
|
566
|
+
function isTerminated(node) {
|
|
567
|
+
return text(node?.status).toLowerCase() === "terminated";
|
|
568
|
+
}
|
|
569
|
+
|
|
681
570
|
function cleanupState(node) {
|
|
682
571
|
return text(node?.cleanup_state).toLowerCase();
|
|
683
572
|
}
|
|
@@ -900,15 +789,6 @@ export async function runAdminNodeLifecycle({
|
|
|
900
789
|
},
|
|
901
790
|
});
|
|
902
791
|
|
|
903
|
-
if (result.status === "declined") {
|
|
904
|
-
return {
|
|
905
|
-
...result,
|
|
906
|
-
action: null,
|
|
907
|
-
resume: null,
|
|
908
|
-
errors: [],
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
|
|
912
792
|
const accepted = {
|
|
913
793
|
operation: command,
|
|
914
794
|
reconciled: result.reconciled === true || lateReconciled,
|
|
@@ -939,7 +819,7 @@ export const adminNodeLifecycleHandlers = Object.keys(COMMAND_BY_CAPABILITY).map
|
|
|
939
819
|
safety:
|
|
940
820
|
COMMAND_BY_CAPABILITY[capabilityId] === "reboot" ||
|
|
941
821
|
COMMAND_BY_CAPABILITY[capabilityId] === "hard-reset"
|
|
942
|
-
? ADMIN_MUTATION_SAFETY.
|
|
822
|
+
? ADMIN_MUTATION_SAFETY.direct
|
|
943
823
|
: ADMIN_MUTATION_SAFETY.reconcile,
|
|
944
824
|
run: runAdminNodeLifecycle,
|
|
945
825
|
})
|