@serve.zone/interfaces 32.68.0 → 32.69.0
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/changelog.md +8 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/clusternode.d.ts +15 -2
- package/dist_ts/data/clusternode.js +1 -1
- package/dist_ts/data/index.d.ts +2 -0
- package/dist_ts/data/index.js +3 -1
- package/dist_ts/data/nodeaction.d.ts +439 -0
- package/dist_ts/data/nodeaction.js +434 -0
- package/dist_ts/data/sparkfleettarget.d.ts +80 -0
- package/dist_ts/data/sparkfleettarget.js +71 -0
- package/dist_ts/data/sparknodereport.d.ts +19 -0
- package/dist_ts/data/sparknodereport.js +21 -4
- package/dist_ts/requests/node.d.ts +100 -0
- package/package.json +1 -1
- package/readme.md +47 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/clusternode.ts +17 -2
- package/ts/data/index.ts +2 -0
- package/ts/data/nodeaction.ts +755 -0
- package/ts/data/sparkfleettarget.ts +138 -0
- package/ts/data/sparknodereport.ts +42 -3
- package/ts/requests/node.ts +118 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { readExactDataRecord } from '../private/dataproperties.js';
|
|
2
|
+
import { compareReleaseVersions, isReleaseVersion, parseReleaseVersion } from './releaseversion.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The Spark release every node of the fleet should run: one row in Cloudly's database, the only
|
|
6
|
+
* source of the release a fresh node's bootstrap installs and an update moves a node to.
|
|
7
|
+
*
|
|
8
|
+
* An administrator names the version and the revision they read; Cloudly reads the SHA-256 of the
|
|
9
|
+
* release's `install.sh` from the release's published `SHA256SUMS.txt`, checks it against the
|
|
10
|
+
* `install.sh` it downloads, and stores the pair. Nobody types a hash. A database without the row
|
|
11
|
+
* holds no target, and every reader refuses by name (`spark-target-unset`) rather than falling
|
|
12
|
+
* back to a release of its own.
|
|
13
|
+
*
|
|
14
|
+
* A target below the release a node runs is admitted: that node reads `ahead` of it, and an update
|
|
15
|
+
* that would move it down is refused (`spark-target-below-node`).
|
|
16
|
+
*/
|
|
17
|
+
export interface ISparkFleetTarget {
|
|
18
|
+
/** `MAJOR.MINOR.PATCH`, without `v`. */
|
|
19
|
+
version: string;
|
|
20
|
+
/** The SHA-256 the release's `SHA256SUMS.txt` states for `install.sh`, lowercase hex. */
|
|
21
|
+
installerSha256: string;
|
|
22
|
+
/** Advances by one with every change; the first target is revision 1. */
|
|
23
|
+
revision: number;
|
|
24
|
+
/** Who set it: `admin:<userId>`, or `migration:<step>` for the target a data step seeded. */
|
|
25
|
+
setBy: string;
|
|
26
|
+
setAt: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One change of the fleet target, kept in an append-only history: the target as it was set, and
|
|
31
|
+
* the version it replaced (`null` for the first). The history is the audit; nothing else records
|
|
32
|
+
* why a target moved.
|
|
33
|
+
*/
|
|
34
|
+
export interface ISparkFleetTargetChange extends ISparkFleetTarget {
|
|
35
|
+
previousVersion: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One node held at the release it runs: it reads `held` rather than behind or ahead, and an update
|
|
40
|
+
* of it is refused (`spark-target-held`) until the hold is released. There is no per-node or
|
|
41
|
+
* per-cluster target; a hold is the only exception to the one fleet target.
|
|
42
|
+
*/
|
|
43
|
+
export interface ISparkNodeTargetHold {
|
|
44
|
+
nodeId: string;
|
|
45
|
+
/** Operator text naming why the node is held. */
|
|
46
|
+
reason: string;
|
|
47
|
+
heldBy: string;
|
|
48
|
+
heldAt: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const sparkFleetTargetContract = Object.freeze({
|
|
52
|
+
/** Changes one `getSparkFleetTarget` answers, newest first. */
|
|
53
|
+
historyLimit: 50,
|
|
54
|
+
/** Characters of one hold's reason. */
|
|
55
|
+
maximumHoldReasonLength: 256,
|
|
56
|
+
} as const);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Every reason Cloudly answers instead of reading or changing the fleet target or a hold. Each
|
|
60
|
+
* travels by name as a `TypedResponseError`, and a refused request changes nothing.
|
|
61
|
+
*/
|
|
62
|
+
export const sparkFleetTargetRefusals = Object.freeze([
|
|
63
|
+
/** The request is not well-formed (`validateSetSparkFleetTargetInput`, `validateSparkNodeTargetHoldInput`). */
|
|
64
|
+
'spark-target-request-invalid',
|
|
65
|
+
/** The target moved since the revision the request names (`0` while there is none). */
|
|
66
|
+
'spark-target-revision-stale',
|
|
67
|
+
/** The release, its `SHA256SUMS.txt` or its `install.sh` could not be downloaded. */
|
|
68
|
+
'spark-release-unavailable',
|
|
69
|
+
/** The release's `SHA256SUMS.txt` states no SHA-256 for `install.sh`. */
|
|
70
|
+
'spark-release-checksum-missing',
|
|
71
|
+
/** The downloaded `install.sh` does not have the SHA-256 its `SHA256SUMS.txt` states. */
|
|
72
|
+
'spark-release-checksum-mismatch',
|
|
73
|
+
/** No node of that id exists. */
|
|
74
|
+
'node-missing',
|
|
75
|
+
] as const);
|
|
76
|
+
|
|
77
|
+
export type TSparkFleetTargetRefusalCode = typeof sparkFleetTargetRefusals[number];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Where one node stands against the fleet target.
|
|
81
|
+
*
|
|
82
|
+
* - `at-target`, `behind`, `ahead`: the Spark bundle version it reports, compared to the target.
|
|
83
|
+
* - `held`: the node is held (`ISparkNodeTargetHold`), whatever it runs.
|
|
84
|
+
* - `unreported`: the node reports no Spark bundle version (it never reported through the node
|
|
85
|
+
* runtime).
|
|
86
|
+
* - `untargeted`: there is no fleet target.
|
|
87
|
+
*/
|
|
88
|
+
export type TSparkNodeTargetStanding = 'at-target' | 'behind' | 'ahead' | 'held' | 'unreported' | 'untargeted';
|
|
89
|
+
|
|
90
|
+
export const readSparkNodeTargetStanding = (argsArg: {
|
|
91
|
+
bundleVersion: string | null;
|
|
92
|
+
target: Pick<ISparkFleetTarget, 'version'> | null;
|
|
93
|
+
held: boolean;
|
|
94
|
+
}): TSparkNodeTargetStanding => {
|
|
95
|
+
if (argsArg.held) return 'held';
|
|
96
|
+
if (argsArg.target === null) return 'untargeted';
|
|
97
|
+
const running = parseReleaseVersion(argsArg.bundleVersion);
|
|
98
|
+
const target = parseReleaseVersion(argsArg.target.version);
|
|
99
|
+
if (running === null || target === null) return 'unreported';
|
|
100
|
+
const order = compareReleaseVersions(running, target);
|
|
101
|
+
return order === 0 ? 'at-target' : order < 0 ? 'behind' : 'ahead';
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validates one `setSparkFleetTarget` input and names every reason it is refused: a release version
|
|
106
|
+
* and the revision of the target the administrator read, `0` while there is none.
|
|
107
|
+
*/
|
|
108
|
+
export const validateSetSparkFleetTargetInput = (
|
|
109
|
+
valueArg: unknown,
|
|
110
|
+
pathArg = 'spark fleet target input',
|
|
111
|
+
): string[] => {
|
|
112
|
+
const value = readExactDataRecord(valueArg, ['version', 'expectedRevision']);
|
|
113
|
+
if (value === null) return [`${pathArg} must use its exact schema`];
|
|
114
|
+
const errors: string[] = [];
|
|
115
|
+
if (!isReleaseVersion(value.version)) errors.push(`${pathArg} version must be a MAJOR.MINOR.PATCH release version`);
|
|
116
|
+
if (!Number.isSafeInteger(value.expectedRevision) || Object.is(value.expectedRevision, -0)
|
|
117
|
+
|| (value.expectedRevision as number) < 0) {
|
|
118
|
+
errors.push(`${pathArg} expectedRevision must be a non-negative integer`);
|
|
119
|
+
}
|
|
120
|
+
return errors;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validates one hold a request states: `{ reason }` to hold the node, `null` to release it.
|
|
125
|
+
*/
|
|
126
|
+
export const validateSparkNodeTargetHoldInput = (
|
|
127
|
+
valueArg: unknown,
|
|
128
|
+
pathArg = 'spark node target hold',
|
|
129
|
+
): string[] => {
|
|
130
|
+
if (valueArg === null) return [];
|
|
131
|
+
const value = readExactDataRecord(valueArg, ['reason']);
|
|
132
|
+
if (value === null) return [`${pathArg} must be null or use its exact schema`];
|
|
133
|
+
return typeof value.reason === 'string' && value.reason.length > 0
|
|
134
|
+
&& value.reason.length <= sparkFleetTargetContract.maximumHoldReasonLength
|
|
135
|
+
&& /^[\x20-\x7e]+$/.test(value.reason)
|
|
136
|
+
? []
|
|
137
|
+
: [`${pathArg} reason must be printable text of 1 to ${sparkFleetTargetContract.maximumHoldReasonLength} characters`];
|
|
138
|
+
};
|
|
@@ -3,6 +3,8 @@ import { readDataProperties, readExactDataRecord } from '../private/dataproperti
|
|
|
3
3
|
import { isClusterNodeId } from '../private/nodeidentifier.js';
|
|
4
4
|
import { protocolRefusalHttpStatus, readProtocolOffer, type IProtocolOffer } from '../protocol.js';
|
|
5
5
|
import { isClusterNodeHostname } from './clusternodehostname.js';
|
|
6
|
+
import type { TNodeActionName } from './clusternode.js';
|
|
7
|
+
import { isHostBootId, isNodeActionNameList } from './nodeaction.js';
|
|
6
8
|
|
|
7
9
|
/** Host observations only. Container and workload readiness belong to Pallet. */
|
|
8
10
|
export interface ISparkNodeReport {
|
|
@@ -28,6 +30,25 @@ export interface ISparkNodeReport {
|
|
|
28
30
|
* it made one since it started. Whether the node carries the hostname is `runtime.hostname`.
|
|
29
31
|
*/
|
|
30
32
|
hostApply?: ISparkNodeHostApply;
|
|
33
|
+
/**
|
|
34
|
+
* The host's boot, stated by a Spark that executes node actions: how Cloudly verifies an action
|
|
35
|
+
* that reboots (`nodeActionVerification`) and whether the host waits for a reboot.
|
|
36
|
+
*/
|
|
37
|
+
boot?: ISparkNodeBoot;
|
|
38
|
+
/**
|
|
39
|
+
* The node actions this Spark executes (`sparkNodeActionContracts`), in canonical order. Cloudly
|
|
40
|
+
* admits a node runtime action only for a node whose report states its executor, so an action
|
|
41
|
+
* Spark cannot execute is refused when it is requested rather than left to expire.
|
|
42
|
+
*/
|
|
43
|
+
actionExecutors?: TNodeActionName[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One boot of the host. */
|
|
47
|
+
export interface ISparkNodeBoot {
|
|
48
|
+
/** The kernel's `boot_id` (`/proc/sys/kernel/random/boot_id`), a lowercase UUID; it changes with every boot. */
|
|
49
|
+
id: string;
|
|
50
|
+
/** Whether the host states that a package it installed waits for a reboot (`/var/run/reboot-required`). */
|
|
51
|
+
rebootRequired: boolean;
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
/** Whether Spark applied a desired hostname: `applied` once the host carries it. */
|
|
@@ -146,8 +167,10 @@ export const snapshotSparkNodeReport = (valueArg: unknown): ISparkNodeReport =>
|
|
|
146
167
|
const reportKeys = ['observedAt', 'metrics', 'runtime'];
|
|
147
168
|
const properties = readDataProperties(valueArg);
|
|
148
169
|
requireValue(properties !== null);
|
|
149
|
-
|
|
150
|
-
|
|
170
|
+
// Each optional member is read exactly when the report states it, so a report without them is
|
|
171
|
+
// the report an earlier Spark sends and is read the way an earlier reader read it.
|
|
172
|
+
const optionalKeys = ['hostApply', 'boot', 'actionExecutors'].filter((keyArg) => Object.hasOwn(properties, keyArg));
|
|
173
|
+
const value = record(valueArg, [...reportKeys, ...optionalKeys]);
|
|
151
174
|
const metrics = record(value.metrics, ['cpuUsagePercent', 'memoryUsedMB', 'memoryAvailableMB',
|
|
152
175
|
'diskUsedGB', 'diskAvailableGB']);
|
|
153
176
|
const runtime = record(value.runtime, ['hostname', 'platform', 'arch', 'osRelease', 'bundle',
|
|
@@ -174,10 +197,26 @@ export const snapshotSparkNodeReport = (valueArg: unknown): ISparkNodeReport =>
|
|
|
174
197
|
}),
|
|
175
198
|
localRuntimeReady: true, workloadReadiness: 'unverified',
|
|
176
199
|
}),
|
|
177
|
-
...(
|
|
200
|
+
...(optionalKeys.includes('hostApply') ? { hostApply: snapshotSparkNodeHostApply(value.hostApply) } : {}),
|
|
201
|
+
...(optionalKeys.includes('boot') ? { boot: snapshotSparkNodeBoot(value.boot) } : {}),
|
|
202
|
+
...(optionalKeys.includes('actionExecutors')
|
|
203
|
+
? { actionExecutors: snapshotActionExecutors(value.actionExecutors) } : {}),
|
|
178
204
|
});
|
|
179
205
|
};
|
|
180
206
|
|
|
207
|
+
/** One `boot`: exactly its keys. */
|
|
208
|
+
const snapshotSparkNodeBoot = (valueArg: unknown): ISparkNodeBoot => {
|
|
209
|
+
const value = record(valueArg, ['id', 'rebootRequired']);
|
|
210
|
+
requireValue(isHostBootId(value.id) && typeof value.rebootRequired === 'boolean');
|
|
211
|
+
return Object.freeze({ id: value.id, rebootRequired: value.rebootRequired });
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** One `actionExecutors`: a canonical list of at least one action name, detached and frozen. */
|
|
215
|
+
const snapshotActionExecutors = (valueArg: unknown): TNodeActionName[] => {
|
|
216
|
+
requireValue(isNodeActionNameList(valueArg));
|
|
217
|
+
return Object.freeze([...valueArg]) as TNodeActionName[];
|
|
218
|
+
};
|
|
219
|
+
|
|
181
220
|
export const snapshotSparkNodeHeartbeatRequest = (valueArg: unknown): ISparkNodeHeartbeatRequest => {
|
|
182
221
|
const value = record(valueArg, ['nodeId', 'nodeToken', 'protocol', 'report']);
|
|
183
222
|
return Object.freeze({
|
package/ts/requests/node.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { IBareMetal } from '../data/baremetal.js';
|
|
|
3
3
|
import type { ICluster } from '../data/cluster.js';
|
|
4
4
|
import type { IClusterNode, INodeAction, INodeMetricsSample, TNodeActionName } from '../data/clusternode.js';
|
|
5
5
|
import type { IDeployment } from '../data/deployment.js';
|
|
6
|
+
import type { INodeActionConfirmation, INodeActionPreview } from '../data/nodeaction.js';
|
|
7
|
+
import type { ISparkFleetTarget, ISparkFleetTargetChange, ISparkNodeTargetHold } from '../data/sparkfleettarget.js';
|
|
6
8
|
import type { IIdentity } from '../data/user.js';
|
|
7
9
|
import type { IPlatformObjectStorageEndpoint } from '../platform/objectstorage.js';
|
|
8
10
|
import type { IPlatformDatabaseEndpoint } from '../platform/database.js';
|
|
@@ -423,6 +425,18 @@ export interface IReq_Any_Cloudly_ResetClusterNodeHostname extends plugins.typed
|
|
|
423
425
|
};
|
|
424
426
|
}
|
|
425
427
|
|
|
428
|
+
/**
|
|
429
|
+
* An administrator requests one action on one node.
|
|
430
|
+
*
|
|
431
|
+
* A node whose Swarm-era daemon reports last receives it as before, in the metrics-sample answer;
|
|
432
|
+
* `confirmation` is not read for it. A node that reports through the node runtime is sent the
|
|
433
|
+
* action only with a `confirmation` (`data.validateNodeActionConfirmation`) of the preview the
|
|
434
|
+
* administrator read (`getNodeActionPreview`): the typed hostname, the impact digest, when the
|
|
435
|
+
* impact names a Corestore on the node the consent to restart it, and for an update the fleet Spark
|
|
436
|
+
* target revision the preview showed. The action then carries
|
|
437
|
+
* `lifecycle`, with the confirmation, the impact and, for an update, the fleet target frozen in it.
|
|
438
|
+
* Every refusal is one of `data.nodeActionRefusals` and changes nothing.
|
|
439
|
+
*/
|
|
426
440
|
export interface IReq_Any_Cloudly_CreateNodeAction extends plugins.typedrequestInterfaces.implementsTR<
|
|
427
441
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
428
442
|
IReq_Any_Cloudly_CreateNodeAction
|
|
@@ -433,12 +447,116 @@ export interface IReq_Any_Cloudly_CreateNodeAction extends plugins.typedrequestI
|
|
|
433
447
|
nodeId: string;
|
|
434
448
|
actionName: TNodeActionName;
|
|
435
449
|
payload?: INodeAction['payload'];
|
|
450
|
+
confirmation?: INodeActionConfirmation;
|
|
451
|
+
};
|
|
452
|
+
response: {
|
|
453
|
+
action: INodeAction;
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* An administrator previews one action on one node that reports through the node runtime: what it
|
|
459
|
+
* interrupts, read from Cloudly's database, the digest a confirmation names, and every reason a
|
|
460
|
+
* request would be refused now. A preview changes nothing.
|
|
461
|
+
*/
|
|
462
|
+
export interface IReq_Any_Cloudly_GetNodeActionPreview extends plugins.typedrequestInterfaces.implementsTR<
|
|
463
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
464
|
+
IReq_Any_Cloudly_GetNodeActionPreview
|
|
465
|
+
> {
|
|
466
|
+
method: 'getNodeActionPreview';
|
|
467
|
+
request: {
|
|
468
|
+
identity: IIdentity;
|
|
469
|
+
nodeId: string;
|
|
470
|
+
actionName: TNodeActionName;
|
|
471
|
+
};
|
|
472
|
+
response: {
|
|
473
|
+
preview: INodeActionPreview;
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* An administrator withdraws one node runtime action while it is still `pending`, at the revision
|
|
479
|
+
* they read. A claimed action is never withdrawn: Spark may already have stopped the node.
|
|
480
|
+
*/
|
|
481
|
+
export interface IReq_Any_Cloudly_CancelNodeAction extends plugins.typedrequestInterfaces.implementsTR<
|
|
482
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
483
|
+
IReq_Any_Cloudly_CancelNodeAction
|
|
484
|
+
> {
|
|
485
|
+
method: 'cancelNodeAction';
|
|
486
|
+
request: {
|
|
487
|
+
identity: IIdentity;
|
|
488
|
+
nodeId: string;
|
|
489
|
+
actionId: string;
|
|
490
|
+
expectedRevision: number;
|
|
436
491
|
};
|
|
437
492
|
response: {
|
|
438
493
|
action: INodeAction;
|
|
439
494
|
};
|
|
440
495
|
}
|
|
441
496
|
|
|
497
|
+
/**
|
|
498
|
+
* An administrator reads the fleet Spark target (`data.ISparkFleetTarget`, `null` while there is
|
|
499
|
+
* none), its newest changes and every node hold.
|
|
500
|
+
*/
|
|
501
|
+
export interface IReq_Any_Cloudly_GetSparkFleetTarget extends plugins.typedrequestInterfaces.implementsTR<
|
|
502
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
503
|
+
IReq_Any_Cloudly_GetSparkFleetTarget
|
|
504
|
+
> {
|
|
505
|
+
method: 'getSparkFleetTarget';
|
|
506
|
+
request: {
|
|
507
|
+
identity: IIdentity;
|
|
508
|
+
};
|
|
509
|
+
response: {
|
|
510
|
+
target: ISparkFleetTarget | null;
|
|
511
|
+
/** Newest first, at most `data.sparkFleetTargetContract.historyLimit`. */
|
|
512
|
+
history: ISparkFleetTargetChange[];
|
|
513
|
+
/** Sorted by node id. */
|
|
514
|
+
holds: ISparkNodeTargetHold[];
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* An administrator sets the fleet Spark target to one release version, at the revision they read
|
|
520
|
+
* (`0` while there is none). Cloudly reads the release's `SHA256SUMS.txt` and `install.sh`, refuses
|
|
521
|
+
* any mismatch between them, and stores the version with the SHA-256 it checked. Every refusal is
|
|
522
|
+
* one of `data.sparkFleetTargetRefusals`.
|
|
523
|
+
*/
|
|
524
|
+
export interface IReq_Any_Cloudly_SetSparkFleetTarget extends plugins.typedrequestInterfaces.implementsTR<
|
|
525
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
526
|
+
IReq_Any_Cloudly_SetSparkFleetTarget
|
|
527
|
+
> {
|
|
528
|
+
method: 'setSparkFleetTarget';
|
|
529
|
+
request: {
|
|
530
|
+
identity: IIdentity;
|
|
531
|
+
/** `data.validateSetSparkFleetTargetInput` judges these two. */
|
|
532
|
+
version: string;
|
|
533
|
+
expectedRevision: number;
|
|
534
|
+
};
|
|
535
|
+
response: {
|
|
536
|
+
target: ISparkFleetTarget;
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* An administrator holds one node at the release it runs (`hold: { reason }`) or releases it
|
|
542
|
+
* (`hold: null`). Answers the hold as it now stands.
|
|
543
|
+
*/
|
|
544
|
+
export interface IReq_Any_Cloudly_SetSparkNodeTargetHold extends plugins.typedrequestInterfaces.implementsTR<
|
|
545
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
546
|
+
IReq_Any_Cloudly_SetSparkNodeTargetHold
|
|
547
|
+
> {
|
|
548
|
+
method: 'setSparkNodeTargetHold';
|
|
549
|
+
request: {
|
|
550
|
+
identity: IIdentity;
|
|
551
|
+
nodeId: string;
|
|
552
|
+
/** `data.validateSparkNodeTargetHoldInput` judges it. */
|
|
553
|
+
hold: { reason: string } | null;
|
|
554
|
+
};
|
|
555
|
+
response: {
|
|
556
|
+
hold: ISparkNodeTargetHold | null;
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
442
560
|
export interface IReq_Any_Cloudly_GetNodeActions extends plugins.typedrequestInterfaces.implementsTR<
|
|
443
561
|
plugins.typedrequestInterfaces.ITypedRequest,
|
|
444
562
|
IReq_Any_Cloudly_GetNodeActions
|