@serve.zone/interfaces 31.8.0 → 31.9.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 +45 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/clusterruntime.d.ts +39 -0
- package/dist_ts/data/clusterruntime.js +11 -0
- package/dist_ts/data/index.d.ts +4 -0
- package/dist_ts/data/index.js +5 -1
- package/dist_ts/data/networkaddressplan.d.ts +78 -0
- package/dist_ts/data/networkaddressplan.js +114 -0
- package/dist_ts/data/networkauthority.js +5 -17
- package/dist_ts/data/networknode.d.ts +37 -0
- package/dist_ts/data/networknode.js +6 -0
- package/dist_ts/data/runtimeconfig.js +14 -77
- package/dist_ts/data/serviceruntime.d.ts +128 -0
- package/dist_ts/data/serviceruntime.js +92 -0
- package/dist_ts/private/networkcontract.d.ts +4 -0
- package/dist_ts/private/networkcontract.js +15 -1
- package/dist_ts/private/producercontract.d.ts +11 -0
- package/dist_ts/private/producercontract.js +21 -0
- package/dist_ts/private/runtimepolicy.d.ts +31 -0
- package/dist_ts/private/runtimepolicy.js +90 -0
- package/dist_ts/requests/cluster.d.ts +37 -0
- package/dist_ts/requests/cluster.js +1 -1
- package/dist_ts/requests/network.d.ts +57 -1
- package/dist_ts/requests/service.d.ts +60 -0
- package/package.json +1 -1
- package/readme.md +120 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/clusterruntime.ts +45 -0
- package/ts/data/index.ts +4 -0
- package/ts/data/networkaddressplan.ts +175 -0
- package/ts/data/networkauthority.ts +4 -16
- package/ts/data/networknode.ts +37 -0
- package/ts/data/runtimeconfig.ts +17 -76
- package/ts/data/serviceruntime.ts +208 -0
- package/ts/private/networkcontract.ts +14 -0
- package/ts/private/producercontract.ts +19 -0
- package/ts/private/runtimepolicy.ts +105 -0
- package/ts/requests/cluster.ts +45 -0
- package/ts/requests/network.ts +50 -1
- package/ts/requests/service.ts +81 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The execution policy a runtime config seals and a service runtime profile states ahead of it.
|
|
3
|
+
* Both validators apply these timing, HTTP request, CPU, memory, identity and log rules. Only the
|
|
4
|
+
* service's target ports decide whether a readiness target exists, which a profile cannot know.
|
|
5
|
+
*
|
|
6
|
+
* Every check here reads captured plain data only, the output of `snapshotBoundedJson` or
|
|
7
|
+
* `captureNetwork`: on live input a getter could pass a check and then hand the caller another value.
|
|
8
|
+
*/
|
|
9
|
+
export const runtimePolicyLimits = Object.freeze({
|
|
10
|
+
minimumMemoryBytes: 16 * 1024 * 1024,
|
|
11
|
+
maximumMemoryBytes: 2 ** 50,
|
|
12
|
+
maximumCpuMillis: 1_000_000,
|
|
13
|
+
maximumIdentityNumber: 2 ** 31 - 1,
|
|
14
|
+
maximumReadinessDurationMs: 3_600_000,
|
|
15
|
+
maximumLogRetentionMs: 31_536_000_000,
|
|
16
|
+
});
|
|
17
|
+
export const isBoundedInteger = (valueArg, minimumArg, maximumArg) => typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && !Object.is(valueArg, -0)
|
|
18
|
+
&& valueArg >= minimumArg && valueArg <= maximumArg;
|
|
19
|
+
export const isRuntimePortName = (valueArg) => typeof valueArg === 'string' && /^[a-z0-9][a-z0-9_.-]{0,62}$/.test(valueArg);
|
|
20
|
+
export const isExactRecord = (valueArg, keysArg) => {
|
|
21
|
+
if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg))
|
|
22
|
+
return false;
|
|
23
|
+
const keys = Object.keys(valueArg);
|
|
24
|
+
return keys.length === keysArg.length && keys.every((keyArg) => keysArg.includes(keyArg));
|
|
25
|
+
};
|
|
26
|
+
/** null explicitly requests no CPU quota; zero is never a quota. */
|
|
27
|
+
export const isRuntimeCpuMillis = (valueArg) => valueArg === null || isBoundedInteger(valueArg, 1, runtimePolicyLimits.maximumCpuMillis);
|
|
28
|
+
export const isRuntimeMemoryBytes = (valueArg) => isBoundedInteger(valueArg, runtimePolicyLimits.minimumMemoryBytes, runtimePolicyLimits.maximumMemoryBytes);
|
|
29
|
+
export const isRuntimeIdentityNumber = (valueArg) => isBoundedInteger(valueArg, 0, runtimePolicyLimits.maximumIdentityNumber);
|
|
30
|
+
export const isRuntimeLogPolicy = (valueArg) => isExactRecord(valueArg, ['maximumLineBytes', 'maximumBufferedBytes', 'retentionMs'])
|
|
31
|
+
&& isBoundedInteger(valueArg.maximumLineBytes, 1, 1_048_576)
|
|
32
|
+
&& isBoundedInteger(valueArg.maximumBufferedBytes, 1, 67_108_864)
|
|
33
|
+
&& valueArg.maximumBufferedBytes >= valueArg.maximumLineBytes
|
|
34
|
+
&& isBoundedInteger(valueArg.retentionMs, 1, runtimePolicyLimits.maximumLogRetentionMs);
|
|
35
|
+
const timingKeys = ['initialDelayMs', 'startupGraceMs', 'intervalMs', 'timeoutMs',
|
|
36
|
+
'successThreshold', 'failureThreshold'];
|
|
37
|
+
/**
|
|
38
|
+
* Readiness policy. `selectsTcpTargetArg` decides whether a probe's named target and port exist:
|
|
39
|
+
* a config answers from its own target ports, a profile ahead of any config cannot know them.
|
|
40
|
+
*/
|
|
41
|
+
export const assertRuntimeReadiness = (valueArg, selectsTcpTargetArg, failArg) => {
|
|
42
|
+
if (!valueArg || typeof valueArg !== 'object')
|
|
43
|
+
failArg('runtime config readiness is required');
|
|
44
|
+
const value = valueArg;
|
|
45
|
+
const keys = value.kind === 'process' ? ['kind', ...timingKeys]
|
|
46
|
+
: value.kind === 'tcp' ? ['kind', ...timingKeys, 'targetPort', 'port']
|
|
47
|
+
: value.kind === 'http' ? ['kind', ...timingKeys, 'targetPort', 'port',
|
|
48
|
+
'scheme', 'host', 'path', 'expectedStatusCodes', 'followRedirects'] : [];
|
|
49
|
+
const maximumDurationMs = runtimePolicyLimits.maximumReadinessDurationMs;
|
|
50
|
+
if (keys.length === 0 || !isExactRecord(value, keys)
|
|
51
|
+
|| !isBoundedInteger(value.initialDelayMs, 0, maximumDurationMs)
|
|
52
|
+
|| !isBoundedInteger(value.startupGraceMs, 0, maximumDurationMs)
|
|
53
|
+
|| !isBoundedInteger(value.intervalMs, 100, maximumDurationMs)
|
|
54
|
+
|| !isBoundedInteger(value.timeoutMs, 1, 60_000)
|
|
55
|
+
|| !isBoundedInteger(value.successThreshold, 1, 100)
|
|
56
|
+
|| !isBoundedInteger(value.failureThreshold, 1, 100)
|
|
57
|
+
|| value.timeoutMs > value.intervalMs || value.initialDelayMs > value.startupGraceMs) {
|
|
58
|
+
failArg('runtime config readiness must use explicit bounded timing and exact schema');
|
|
59
|
+
}
|
|
60
|
+
if (value.kind === 'process')
|
|
61
|
+
return;
|
|
62
|
+
if (!isRuntimePortName(value.targetPort) || !isBoundedInteger(value.port, 1, 65535)
|
|
63
|
+
|| !selectsTcpTargetArg(value.targetPort, value.port)) {
|
|
64
|
+
failArg('runtime config readiness must select a declared TCP target and port');
|
|
65
|
+
}
|
|
66
|
+
if (value.kind === 'tcp')
|
|
67
|
+
return;
|
|
68
|
+
if ((value.scheme !== 'http' && value.scheme !== 'https') || value.followRedirects !== false
|
|
69
|
+
|| typeof value.host !== 'string' || value.host.length > 253
|
|
70
|
+
|| !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/.test(value.host)
|
|
71
|
+
|| typeof value.path !== 'string' || value.path.length > 4096
|
|
72
|
+
|| !value.path.startsWith('/') || value.path.startsWith('//')
|
|
73
|
+
|| /[^\x21-\x7e]|[\\#]/.test(value.path)
|
|
74
|
+
|| /%(?![0-9A-F]{2})/.test(value.path)
|
|
75
|
+
|| !Array.isArray(value.expectedStatusCodes) || value.expectedStatusCodes.length === 0
|
|
76
|
+
|| value.expectedStatusCodes.length > 32) {
|
|
77
|
+
failArg('runtime config HTTP readiness must use an exact bounded request policy');
|
|
78
|
+
}
|
|
79
|
+
const parsed = new URL(value.path, 'http://runtime.invalid');
|
|
80
|
+
if (`${parsed.pathname}${parsed.search}` !== value.path)
|
|
81
|
+
failArg('runtime config HTTP path must be canonical');
|
|
82
|
+
let previousCode = 0;
|
|
83
|
+
for (const code of value.expectedStatusCodes) {
|
|
84
|
+
if (!isBoundedInteger(code, 200, 599) || code <= previousCode) {
|
|
85
|
+
failArg('runtime config HTTP status expectations must be sorted unique final codes');
|
|
86
|
+
}
|
|
87
|
+
previousCode = code;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZXBvbGljeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3ByaXZhdGUvcnVudGltZXBvbGljeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQTs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUMvQyxrQkFBa0IsRUFBRSxFQUFFLEdBQUcsSUFBSSxHQUFHLElBQUk7SUFDcEMsa0JBQWtCLEVBQUUsQ0FBQyxJQUFJLEVBQUU7SUFDM0IsZ0JBQWdCLEVBQUUsU0FBUztJQUMzQixxQkFBcUIsRUFBRSxDQUFDLElBQUksRUFBRSxHQUFHLENBQUM7SUFDbEMsMEJBQTBCLEVBQUUsU0FBUztJQUNyQyxxQkFBcUIsRUFBRSxjQUFjO0NBQzdCLENBQUMsQ0FBQztBQUlaLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsUUFBaUIsRUFBRSxVQUFrQixFQUFFLFVBQWtCLEVBQXNCLEVBQUUsQ0FDaEgsT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQztPQUN2RixRQUFRLElBQUksVUFBVSxJQUFJLFFBQVEsSUFBSSxVQUFVLENBQUM7QUFDdEQsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsQ0FBQyxRQUFpQixFQUFzQixFQUFFLENBQ3pFLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSw2QkFBNkIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDL0UsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLENBQzNCLFFBQWlCLEVBQUUsT0FBMEIsRUFDUixFQUFFO0lBQ3ZDLElBQUksQ0FBQyxRQUFRLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxD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
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as userInterfaces from '../data/user.js';
|
|
2
2
|
import * as clusterInterfaces from '../data/cluster.js';
|
|
3
|
+
import type { IClusterRuntime, TClusterRuntimeBlocker } from '../data/clusterruntime.js';
|
|
4
|
+
import type { IServiceRuntimeAssignmentView } from '../data/serviceruntime.js';
|
|
3
5
|
import type * as leaseInterfaces from '../data/networklease.js';
|
|
4
6
|
import * as plugins from '../plugins.js';
|
|
5
7
|
/**
|
|
@@ -141,3 +143,38 @@ export interface IReq_Any_Cloudly_DeleteClusterById extends plugins.typedrequest
|
|
|
141
143
|
ok: boolean;
|
|
142
144
|
};
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* A cluster's runtime phase, and what still blocks it from leaving `swarm`.
|
|
148
|
+
*
|
|
149
|
+
* Cloudly refuses a cluster without a phase record as `cluster-runtime-missing` rather than
|
|
150
|
+
* assuming one. `blockers` is empty once nothing blocks the cutover.
|
|
151
|
+
*/
|
|
152
|
+
export interface IReq_Any_Cloudly_GetClusterRuntime extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetClusterRuntime> {
|
|
153
|
+
method: 'getClusterRuntime';
|
|
154
|
+
request: {
|
|
155
|
+
identity: userInterfaces.IIdentity;
|
|
156
|
+
clusterId: string;
|
|
157
|
+
};
|
|
158
|
+
response: {
|
|
159
|
+
runtime: IClusterRuntime;
|
|
160
|
+
blockers: TClusterRuntimeBlocker[];
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Every current runtime assignment on one cluster's nodes, a page at a time: a cluster carries
|
|
165
|
+
* every node's slots of every service, which no single response bounds. `limit` runs from 1 to
|
|
166
|
+
* `serviceRuntimeContract.maximumAssignmentPageSize`; Cloudly refuses any other.
|
|
167
|
+
*/
|
|
168
|
+
export interface IReq_Any_Cloudly_GetClusterRuntimeAssignments extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetClusterRuntimeAssignments> {
|
|
169
|
+
method: 'getClusterRuntimeAssignments';
|
|
170
|
+
request: {
|
|
171
|
+
identity: userInterfaces.IIdentity;
|
|
172
|
+
clusterId: string;
|
|
173
|
+
cursor: string | null;
|
|
174
|
+
limit: number;
|
|
175
|
+
};
|
|
176
|
+
response: {
|
|
177
|
+
assignments: IServiceRuntimeAssignmentView[];
|
|
178
|
+
nextCursor: string | null;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import * as userInterfaces from '../data/user.js';
|
|
2
2
|
import * as clusterInterfaces from '../data/cluster.js';
|
|
3
3
|
import * as plugins from '../plugins.js';
|
|
4
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
4
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JlcXVlc3RzL2NsdXN0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLGNBQWMsTUFBTSxpQkFBaUIsQ0FBQztBQUNsRCxPQUFPLEtBQUssaUJBQWlCLE1BQU0sb0JBQW9CLENBQUM7QUFJeEQsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUMifQ==
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import type { IIdentityCredential } from '../data/user.js';
|
|
2
|
+
import type { IIdentity, IIdentityCredential } from '../data/user.js';
|
|
3
|
+
import type { IRuntimeNetworkAddressPlan, TRuntimeNetworkAddressPlanWritableData } from '../data/networkaddressplan.js';
|
|
4
|
+
import type { IRuntimeNetworkNodeReadiness, IRuntimeRouterIncarnation } from '../data/networknode.js';
|
|
3
5
|
import type { IPrivateNetworkV1, IServicePrivateNetworksV1, ISetServicePrivateNetworksV1, TPrivateNetworkMutationV1 } from '../data/privatenetwork.js';
|
|
4
6
|
import type { IServicePublishedPortsV1, ISetServicePublishedPortsV1 } from '../data/networkprojection.js';
|
|
5
7
|
/** Current organization picker data. Entries are never cached authorization grants. */
|
|
@@ -130,3 +132,57 @@ export interface IRequest_Any_Cloudly_GetNetworkNodes {
|
|
|
130
132
|
networkNodes: plugins.tsclass.network.INetworkNode[];
|
|
131
133
|
};
|
|
132
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Writes the runtime network's address plan.
|
|
137
|
+
*
|
|
138
|
+
* `expectedRevision` is the revision the caller read, or `null` when no plan exists yet. Cloudly
|
|
139
|
+
* validates the plan with `snapshotRuntimeNetworkAddressPlan`, decides the change with
|
|
140
|
+
* `admitRuntimeNetworkAddressPlanChange`, refuses the change while a resolver or platform endpoint
|
|
141
|
+
* it removes or changes is still referenced, and answers with the stored plan: at its new revision,
|
|
142
|
+
* or unchanged.
|
|
143
|
+
*/
|
|
144
|
+
export interface IReq_Any_Cloudly_SetRuntimeNetworkAddressPlan extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_SetRuntimeNetworkAddressPlan> {
|
|
145
|
+
method: 'setRuntimeNetworkAddressPlan';
|
|
146
|
+
request: {
|
|
147
|
+
identity: IIdentity;
|
|
148
|
+
expectedRevision: number | null;
|
|
149
|
+
plan: TRuntimeNetworkAddressPlanWritableData;
|
|
150
|
+
};
|
|
151
|
+
response: {
|
|
152
|
+
plan: IRuntimeNetworkAddressPlan;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** `null` until an administrator sets the first plan; the runtime network stays unconfigured until then. */
|
|
156
|
+
export interface IReq_Any_Cloudly_GetRuntimeNetworkAddressPlan extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetRuntimeNetworkAddressPlan> {
|
|
157
|
+
method: 'getRuntimeNetworkAddressPlan';
|
|
158
|
+
request: {
|
|
159
|
+
identity: IIdentity;
|
|
160
|
+
};
|
|
161
|
+
response: {
|
|
162
|
+
plan: IRuntimeNetworkAddressPlan | null;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Whether one node's runtime network is ready for workloads, and every condition still missing. */
|
|
166
|
+
export interface IReq_Any_Cloudly_GetRuntimeNetworkNodeReadiness extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_GetRuntimeNetworkNodeReadiness> {
|
|
167
|
+
method: 'getRuntimeNetworkNodeReadiness';
|
|
168
|
+
request: {
|
|
169
|
+
identity: IIdentity;
|
|
170
|
+
nodeId: string;
|
|
171
|
+
};
|
|
172
|
+
response: IRuntimeNetworkNodeReadiness;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Advances one node's router incarnation, so the node producer reserves the router a fresh handoff.
|
|
176
|
+
* `expectedIncarnation` is the incarnation the caller read; any other refuses the request.
|
|
177
|
+
*/
|
|
178
|
+
export interface IReq_Any_Cloudly_ReincarnateRuntimeNetworkRouter extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_ReincarnateRuntimeNetworkRouter> {
|
|
179
|
+
method: 'reincarnateRuntimeNetworkRouter';
|
|
180
|
+
request: {
|
|
181
|
+
identity: IIdentity;
|
|
182
|
+
nodeId: string;
|
|
183
|
+
expectedIncarnation: number;
|
|
184
|
+
};
|
|
185
|
+
response: {
|
|
186
|
+
incarnation: IRuntimeRouterIncarnation;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -8,6 +8,7 @@ import type { IImageRelease } from '../data/immutableimage.js';
|
|
|
8
8
|
import type { IDeploymentPreflightReport, IDeploymentPreflightRequestData } from '../data/deploymentpreflight.js';
|
|
9
9
|
import type { IDeploymentReservationInput, IServiceDeploymentOperation, IServiceDeploymentStatus } from '../data/deploymentoperation.js';
|
|
10
10
|
import type { TSha256Digest } from '../data/immutableimage.js';
|
|
11
|
+
import type { IServiceRuntimeAssignmentView, IServiceRuntimeSpec, IServiceRuntimeStatus, TServiceRuntimeSpecWritableData } from '../data/serviceruntime.js';
|
|
11
12
|
import type { IServiceDeploymentAdoptionInput, IServiceDeploymentAdoptionImagePromotionInput, IServiceDeploymentAdoptionImagePromotionResult, IServiceDeploymentAdoptionRolloutInput, IServiceDeploymentAdoptionRolloutResult, IServiceDeploymentAdoptionPreflightInput, IServiceDeploymentAdoptionReport, IServiceDeploymentAdoptionResult } from '../data/deploymentadoption.js';
|
|
12
13
|
export interface IRequest_Any_Cloudly_BootstrapServiceDeploymentAdoptionRollout extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_BootstrapServiceDeploymentAdoptionRollout> {
|
|
13
14
|
method: 'bootstrapServiceDeploymentAdoptionRollout';
|
|
@@ -253,3 +254,62 @@ export interface IRequest_Any_Cloudly_DeleteServiceById extends plugins.typedreq
|
|
|
253
254
|
success: boolean;
|
|
254
255
|
};
|
|
255
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Writes the complete runtime spec of one service.
|
|
259
|
+
*
|
|
260
|
+
* `expectedRevision` is the revision the caller read, or `null` when the service has never had a
|
|
261
|
+
* spec; any other stored revision refuses the write. Cloudly copies the organization from the
|
|
262
|
+
* service and answers with the stored spec at its new revision.
|
|
263
|
+
*/
|
|
264
|
+
export interface IRequest_Any_Cloudly_SetServiceRuntimeSpec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_SetServiceRuntimeSpec> {
|
|
265
|
+
method: 'setServiceRuntimeSpec';
|
|
266
|
+
request: {
|
|
267
|
+
identity: IIdentity;
|
|
268
|
+
serviceId: string;
|
|
269
|
+
expectedRevision: number | null;
|
|
270
|
+
spec: TServiceRuntimeSpecWritableData;
|
|
271
|
+
};
|
|
272
|
+
response: {
|
|
273
|
+
spec: IServiceRuntimeSpec;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* A service's runtime spec and the producer's verdict on it. Both are `null` before the first spec;
|
|
278
|
+
* a spec's status is `null` until the producer first decides it.
|
|
279
|
+
*/
|
|
280
|
+
export interface IRequest_Any_Cloudly_GetServiceRuntimeSpec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeSpec> {
|
|
281
|
+
method: 'getServiceRuntimeSpec';
|
|
282
|
+
request: {
|
|
283
|
+
identity: IIdentity;
|
|
284
|
+
serviceId: string;
|
|
285
|
+
};
|
|
286
|
+
response: {
|
|
287
|
+
spec: null;
|
|
288
|
+
status: null;
|
|
289
|
+
} | {
|
|
290
|
+
spec: IServiceRuntimeSpec;
|
|
291
|
+
status: IServiceRuntimeStatus | null;
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/** Every current runtime assignment of one service, one per replica slot. */
|
|
295
|
+
export interface IRequest_Any_Cloudly_GetServiceRuntimeAssignments extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeAssignments> {
|
|
296
|
+
method: 'getServiceRuntimeAssignments';
|
|
297
|
+
request: {
|
|
298
|
+
identity: IIdentity;
|
|
299
|
+
serviceId: string;
|
|
300
|
+
};
|
|
301
|
+
response: {
|
|
302
|
+
assignments: IServiceRuntimeAssignmentView[];
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Cloudly -> admin UIs: a service's runtime spec, status or assignments changed. It carries no
|
|
307
|
+
* state, so a late or repeated push can never overwrite a newer read; the UI reads the views again.
|
|
308
|
+
*/
|
|
309
|
+
export interface IReq_Cloudly_Any_PushServiceRuntimeChanged extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_Any_PushServiceRuntimeChanged> {
|
|
310
|
+
method: 'pushServiceRuntimeChanged';
|
|
311
|
+
request: {
|
|
312
|
+
serviceId: string;
|
|
313
|
+
};
|
|
314
|
+
response: Record<string, never>;
|
|
315
|
+
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -594,6 +594,62 @@ const { renewal: admittedRenewal } = await data.admitRuntimeNetworkDnsLeaseRenew
|
|
|
594
594
|
const window = await data.getRuntimeNetworkEffectiveDnsWindow(admittedProjection, admittedRenewal);
|
|
595
595
|
```
|
|
596
596
|
|
|
597
|
+
### Runtime Network Address Plan
|
|
598
|
+
|
|
599
|
+
`data.IRuntimeNetworkAddressPlan` is the address space an administrator gives the runtime network,
|
|
600
|
+
and the single input Cloudly composes the protected authority from:
|
|
601
|
+
`{ id: 'address-plan', revision, prefixes, pools, resolvers, platformEndpoints, vpn: { controlPrefix, relay } }`.
|
|
602
|
+
Pools, resolvers and platform endpoints use the protected authority's own element shapes, and the
|
|
603
|
+
relay uses the managed-VPN credential's `{ id, address, port }`. `platformEndpoints` never lists the
|
|
604
|
+
relay: the composed authority adds it, in id order, as the UDP platform endpoint every node dials.
|
|
605
|
+
|
|
606
|
+
`snapshotRuntimeNetworkAddressPlan` refuses any plan that could describe an authority the authority
|
|
607
|
+
contract refuses. It runs `snapshotRuntimeNetworkProtectedAuthorityV1` twice, once over the plan's
|
|
608
|
+
own sets and once with the relay added, so overlapping pools, more than 16 resolvers, a resolver or
|
|
609
|
+
endpoint outside `prefixes` or inside a pool, and a relay that `prefixes` do not cover are all
|
|
610
|
+
refused by that one validator. On top it requires the VPN `controlPrefix` to be a private prefix no
|
|
611
|
+
longer than /30 (`runtimeNetworkAddressPlanContract.maximumControlPrefixLength`) inside `prefixes`
|
|
612
|
+
that overlaps no pool and holds no resolver or platform endpoint, the relay included. No minimum
|
|
613
|
+
length is stated: the private-range rule already keeps it at /8 or longer.
|
|
614
|
+
`composeRuntimeNetworkProtectedAuthority(plan, frame)` is the composition itself: the producer
|
|
615
|
+
supplies the frame (`TRuntimeNetworkProtectedAuthorityFrame`: authority id, generation, controller,
|
|
616
|
+
previous reference and egress authorities) and receives the complete authority with its digest, so
|
|
617
|
+
the validator and the protection producer can never compose differently.
|
|
618
|
+
|
|
619
|
+
`admitRuntimeNetworkAddressPlanChange(previous | null, next)` is the pure decision for a plan write at
|
|
620
|
+
the following revision; any other revision is a contract violation. Leases and the managed VPN
|
|
621
|
+
outlive a plan revision, so it answers:
|
|
622
|
+
|
|
623
|
+
- `unchanged` when the content equals the stored plan; Cloudly writes nothing;
|
|
624
|
+
- `refused` with `plan-vpn-immutable` when the VPN differs, `plan-pool-removed` when any pool is gone
|
|
625
|
+
or changed its purpose or prefix, and `plan-prefix-shrunk` when the new prefixes, however split,
|
|
626
|
+
no longer cover every address the old ones did;
|
|
627
|
+
- `accepted` with the ids of the resolvers and platform endpoints the change removes or changes. An
|
|
628
|
+
entry that keeps its id but differs in any field counts, so moving it goes through the same
|
|
629
|
+
reference check as removing it.
|
|
630
|
+
|
|
631
|
+
`TRuntimeNetworkAddressPlanRefusal` adds `plan-resolver-referenced` and `plan-endpoint-referenced`,
|
|
632
|
+
which only Cloudly can decide against live references, so every plan refusal has one type.
|
|
633
|
+
`requests.network` carries `setRuntimeNetworkAddressPlan { identity, expectedRevision, plan }`
|
|
634
|
+
(`expectedRevision: null` when no plan exists yet) and `getRuntimeNetworkAddressPlan`, which answers
|
|
635
|
+
`null` until the first plan is set.
|
|
636
|
+
|
|
637
|
+
### Node Network Readiness
|
|
638
|
+
|
|
639
|
+
`requests.network.getRuntimeNetworkNodeReadiness { identity, nodeId }` answers
|
|
640
|
+
`data.IRuntimeNetworkNodeReadiness`: `ready: true` with nothing missing, or `ready: false` with at
|
|
641
|
+
least one `TRuntimeNetworkNodeReadinessGap`. The gaps follow the node producer's order: the node
|
|
642
|
+
joins the protected authority's egress (`egress-pending`), every egress owner reports current
|
|
643
|
+
protection (`protection-receipts-pending`, naming those `owners`, at least one), a handoff is reserved
|
|
644
|
+
(`handoff-pending`), the router selection carries it (`selection-pending`), the node's acknowledged
|
|
645
|
+
projection carries it (`projection-pending`), and the node is a managed VPN member
|
|
646
|
+
(`vpn-member-pending`).
|
|
647
|
+
|
|
648
|
+
`data.IRuntimeRouterIncarnation { id, incarnation }` is a node's router incarnation, from which its
|
|
649
|
+
handoff and router incarnation ids derive; `snapshotRuntimeRouterIncarnation` checks the record.
|
|
650
|
+
`reincarnateRuntimeNetworkRouter { identity, nodeId, expectedIncarnation }` advances it, so the node
|
|
651
|
+
producer reserves the router a fresh handoff, and answers with the new incarnation.
|
|
652
|
+
|
|
597
653
|
### Node-Bound Runtime Assignments
|
|
598
654
|
|
|
599
655
|
The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
|
|
@@ -727,6 +783,70 @@ only the named Pallet attempt. They do not authorize storage relocation, prove
|
|
|
727
783
|
whole-host or Docker/Coreflow absence, grant another controller or organization
|
|
728
784
|
authority, or replace independently qualified readiness and routing evidence.
|
|
729
785
|
|
|
786
|
+
### Cluster Runtime Phase
|
|
787
|
+
|
|
788
|
+
`data.IClusterRuntime { id, phase, generation, changedAt, changedBy }` records which runtime owns a
|
|
789
|
+
cluster's workloads: `TClusterRuntimePhase` is `swarm`, `draining`, `switching` or `pallet`. It is
|
|
790
|
+
stored beside the cluster document, because a cluster update merges caller data and no caller may
|
|
791
|
+
move a phase with it; `generation` is its compare-and-swap counter. `snapshotClusterRuntime` checks
|
|
792
|
+
the record. Which phase may follow which belongs to the cutover, whose requests ship with it.
|
|
793
|
+
|
|
794
|
+
`requests.cluster.getClusterRuntime { identity, clusterId }` answers `{ runtime, blockers }`. Each
|
|
795
|
+
`TClusterRuntimeBlocker` names what it is about: cluster-wide blockers (`relay-not-enabled`,
|
|
796
|
+
`relay-not-registered`, `address-plan-missing`, `vpn-unconfigured`) name nothing, node blockers
|
|
797
|
+
(`node-without-pallet-credential`, `node-arch-unsupported`) name the `nodeId`, and service blockers
|
|
798
|
+
(`service-multi-cluster-scope`, `service-authority-unsupported`, `platform-binding-present`) name
|
|
799
|
+
the `serviceId`.
|
|
800
|
+
|
|
801
|
+
### Service Runtime Specs And Status
|
|
802
|
+
|
|
803
|
+
`data.IServiceRuntimeSpec` is what Cloudly runs for one service on a Pallet cluster:
|
|
804
|
+
`{ id, organizationId, clusterId, placement, replicasPerNode, hold, profile, network, revision }`.
|
|
805
|
+
It is stored complete and beside the service document, so a hold written by a cutover never races
|
|
806
|
+
an operator's service edit.
|
|
807
|
+
|
|
808
|
+
- `placement` is `{ kind: 'every-node' }` or `{ kind: 'nodes', nodeIds }`, with `nodeIds` sorted,
|
|
809
|
+
unique, never empty and at most `maximumPinnedNodes`, the protected authority's egress limit.
|
|
810
|
+
- `replicasPerNode` runs from 1 to 64; Cloudly refuses a service desiring more than `maximumReplicas` (64) as `replica-limit`.
|
|
811
|
+
- `profile { cpuMillis, memoryBytes, readiness, logs, readonlyRootfs, runAs }` is the runtime config's
|
|
812
|
+
execution policy ahead of any image: the same readiness and log shapes, and the same CPU, memory
|
|
813
|
+
(at least 16 MiB), identity, log, readiness timing and HTTP request rules, which both validators
|
|
814
|
+
share. `runAs: null` runs the image's own OCI user. Only the service's target ports decide whether
|
|
815
|
+
a readiness probe's target exists: the runtime config requires the service to declare it, and the
|
|
816
|
+
producer refuses a service that does not as `readiness-target-undeclared`.
|
|
817
|
+
- `network` is `{ mode: 'isolated' }` or `{ mode: 'attached', publicEgress, platformEndpointIds }`
|
|
818
|
+
with sorted unique endpoint ids.
|
|
819
|
+
|
|
820
|
+
`snapshotServiceRuntimeSpec` checks all of that; whether the cluster, the nodes and the endpoints
|
|
821
|
+
exist is Cloudly's. `requests.service.setServiceRuntimeSpec { identity, serviceId, expectedRevision, spec }`
|
|
822
|
+
writes `TServiceRuntimeSpecWritableData`, and Cloudly copies the organization from the service.
|
|
823
|
+
`getServiceRuntimeSpec` answers `{ spec, status }`: both `null` before the first spec, and `status`
|
|
824
|
+
`null` until the producer first decides the spec. A status never comes without its spec.
|
|
825
|
+
|
|
826
|
+
`data.IServiceRuntimeStatus { id, specRevision, state, reason, changedAt }` is the producer's verdict
|
|
827
|
+
on one spec revision, and each reason belongs to exactly one state. `converged` and `converging`
|
|
828
|
+
carry `reason: null`. `waiting` carries a `TServiceRuntimeWaitingReason`: `node-network-pending`,
|
|
829
|
+
`conflict` or `failure-backoff`. `refused` carries a `TServiceRuntimeRefusal`:
|
|
830
|
+
`cluster-runtime-missing`, `organization-missing`, `image-plan-missing`, `platform-unsupported`,
|
|
831
|
+
`secrets-unsupported`, `volumes-unsupported`, `port-protocol-unsupported`,
|
|
832
|
+
`readiness-target-undeclared`, `environment-too-large`, `no-eligible-node` or `replica-limit`. A
|
|
833
|
+
refusal never retires running slots. `snapshotServiceRuntimeStatus` enforces the pairing.
|
|
834
|
+
|
|
835
|
+
### Runtime Assignment Views
|
|
836
|
+
|
|
837
|
+
`data.IServiceRuntimeAssignmentView` is one replica slot's current assignment as an administrator
|
|
838
|
+
reads it: `serviceId`, `replicaId`, `nodeId`, the current `assignment` reference, its `disposition`,
|
|
839
|
+
the latest accepted `observation { phase, ready, observedAt, sequence }` (which may cite an earlier
|
|
840
|
+
revision), `terminalReceiptCount` (0 to 2) and `delivery`. `delivered` means the node's own evidence,
|
|
841
|
+
an observation or a terminal receipt, cites the current revision; until then it is `undelivered`.
|
|
842
|
+
|
|
843
|
+
`requests.service.getServiceRuntimeAssignments { identity, serviceId }` answers every slot of one
|
|
844
|
+
service. `requests.cluster.getClusterRuntimeAssignments { identity, clusterId, cursor, limit }` pages
|
|
845
|
+
through every slot on a cluster's nodes; `limit` runs from 1 to
|
|
846
|
+
`serviceRuntimeContract.maximumAssignmentPageSize` (256), and Cloudly refuses any other.
|
|
847
|
+
`requests.service.pushServiceRuntimeChanged { serviceId }` tells admin UIs that a service's spec,
|
|
848
|
+
status or assignments changed; it carries no state, so a late push never overwrites a newer read.
|
|
849
|
+
|
|
730
850
|
### Offline Database Settlement And Packaging
|
|
731
851
|
|
|
732
852
|
The Node-only `@serve.zone/interfaces/runtime` export includes isolated V1
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { captureNetwork, networkCounter, networkId, networkRecord, requireNetwork } from '../private/networkcontract.js';
|
|
2
|
+
import { isOneOf, producerSafe } from '../private/producercontract.js';
|
|
3
|
+
|
|
4
|
+
const clusterRuntimePhases = ['swarm', 'draining', 'switching', 'pallet'] as const;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Which runtime owns a cluster's workloads.
|
|
8
|
+
*
|
|
9
|
+
* `swarm` is Coreflow's; `draining` stops every service while its volumes stay; `switching` lets the
|
|
10
|
+
* cluster's nodes join the runtime network while nothing runs; `pallet` is Pallet's alone.
|
|
11
|
+
*/
|
|
12
|
+
export type TClusterRuntimePhase = typeof clusterRuntimePhases[number];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The runtime phase of one cluster, stored beside the cluster document rather than inside it: a
|
|
16
|
+
* cluster update merges caller data, and no caller may move a phase with it.
|
|
17
|
+
*/
|
|
18
|
+
export interface IClusterRuntime {
|
|
19
|
+
/** The cluster id. */
|
|
20
|
+
id: string;
|
|
21
|
+
phase: TClusterRuntimePhase;
|
|
22
|
+
/** Compare-and-swap counter; every phase write advances it by one. */
|
|
23
|
+
generation: number;
|
|
24
|
+
/** Unix milliseconds on Cloudly's clock. */
|
|
25
|
+
changedAt: number;
|
|
26
|
+
/** The administrator or producer that made the change. */
|
|
27
|
+
changedBy: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Why a cluster cannot leave `swarm` yet. Each blocker names the node or service it is about, so an
|
|
32
|
+
* administrator can act on it without a second query.
|
|
33
|
+
*/
|
|
34
|
+
export type TClusterRuntimeBlocker =
|
|
35
|
+
| { code: 'relay-not-enabled' | 'relay-not-registered' | 'address-plan-missing' | 'vpn-unconfigured' }
|
|
36
|
+
| { code: 'node-without-pallet-credential' | 'node-arch-unsupported'; nodeId: string }
|
|
37
|
+
| { code: 'service-multi-cluster-scope' | 'service-authority-unsupported' | 'platform-binding-present'; serviceId: string };
|
|
38
|
+
|
|
39
|
+
/** Structural snapshot for exact persistence; which phase may follow which is the cutover's decision. */
|
|
40
|
+
export const snapshotClusterRuntime = (value: unknown): IClusterRuntime => producerSafe(() => {
|
|
41
|
+
const item = networkRecord(captureNetwork(value), ['id', 'phase', 'generation', 'changedAt', 'changedBy']);
|
|
42
|
+
requireNetwork(isOneOf(clusterRuntimePhases, item.phase));
|
|
43
|
+
return { id: networkId(item.id), phase: item.phase, generation: networkCounter(item.generation),
|
|
44
|
+
changedAt: networkCounter(item.changedAt), changedBy: networkId(item.changedBy) };
|
|
45
|
+
});
|
package/ts/data/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './cloudlyconfig.js';
|
|
2
2
|
export * from './cluster.js';
|
|
3
|
+
export * from './clusterruntime.js';
|
|
3
4
|
export * from './clusteringress.js';
|
|
4
5
|
export * from './config.js';
|
|
5
6
|
export * from './corestore.js';
|
|
@@ -24,10 +25,12 @@ export * from './isolatedrestore.golden.js';
|
|
|
24
25
|
export * from './mail.js';
|
|
25
26
|
export * from './nodecredential.js';
|
|
26
27
|
export * from './nodeenrollment.js';
|
|
28
|
+
export * from './networkaddressplan.js';
|
|
27
29
|
export * from './networkauthority.js';
|
|
28
30
|
export * from './networkcredential.js';
|
|
29
31
|
export * from './networkdelivery.js';
|
|
30
32
|
export * from './networklease.js';
|
|
33
|
+
export * from './networknode.js';
|
|
31
34
|
export * from './networkprojection.js';
|
|
32
35
|
export type { IRuntimeNetworkRouterPacketGrantV1, IRuntimeNetworkRouterProjectionV1 } from './networkrouter.js';
|
|
33
36
|
export * from './networkprotection.js';
|
|
@@ -48,6 +51,7 @@ export * from './sparknodereport.js';
|
|
|
48
51
|
export * from './service.js';
|
|
49
52
|
export * from './servicecredential.js';
|
|
50
53
|
export * from './serviceports.js';
|
|
54
|
+
export * from './serviceruntime.js';
|
|
51
55
|
export * from './status.js';
|
|
52
56
|
export * from './taskexecution.js';
|
|
53
57
|
export * from './traffic.js';
|