@serve.zone/interfaces 32.62.0 → 32.64.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 +14 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/clusteringress.d.ts +7 -1
- package/dist_ts/data/clusteringress.js +15 -1
- package/dist_ts/data/index.d.ts +2 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/networkprivatemesh.d.ts +27 -0
- package/dist_ts/data/networkprivatemesh.js +27 -0
- package/dist_ts/data/networkprojection.d.ts +8 -0
- package/dist_ts/data/networkprojection.js +30 -13
- package/dist_ts/data/runtimeassignment.d.ts +24 -0
- package/dist_ts/data/runtimeassignment.js +18 -2
- package/dist_ts/data/runtimesessionpush.d.ts +24 -0
- package/dist_ts/data/runtimesessionpush.js +25 -0
- package/package.json +1 -1
- package/readme.md +49 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/clusteringress.ts +14 -0
- package/ts/data/index.ts +2 -0
- package/ts/data/networkprivatemesh.ts +45 -0
- package/ts/data/networkprojection.ts +27 -11
- package/ts/data/runtimeassignment.ts +33 -1
- package/ts/data/runtimesessionpush.ts +24 -0
package/readme.md
CHANGED
|
@@ -627,6 +627,26 @@ locally, but only to a workload that selects it.
|
|
|
627
627
|
|
|
628
628
|
`getRuntimeNetworkProjectionPacketGrants` derives directed member connectivity
|
|
629
629
|
and explicit local public/platform egress independently of DNS readiness.
|
|
630
|
+
|
|
631
|
+
`getRuntimeNetworkProjectionPrivateMeshes` validates the complete projection and
|
|
632
|
+
returns its private meshes (`IRuntimeNetworkPrivateMesh`): one per
|
|
633
|
+
`(organizationId, networkId)` that some endpoint's service membership attaches,
|
|
634
|
+
with the lease references of every such endpoint, local or remote, sorted by
|
|
635
|
+
lease id. Meshes are sorted by organization and then network id; a network no
|
|
636
|
+
endpoint attaches has no mesh, and an endpoint whose membership attaches no
|
|
637
|
+
network is in none. This grouping is the one definition of who may reach whom:
|
|
638
|
+
the workload grants of `getRuntimeNetworkProjectionPacketGrants` are exactly the
|
|
639
|
+
ordered pairs of distinct members of each mesh with at least one end on the
|
|
640
|
+
projection's node, each pair once however many networks its ends share. A
|
|
641
|
+
receiver realizes a mesh from it without deriving membership again. Whether a
|
|
642
|
+
member is local is its lease's node scope (the projection's `nodeId`,
|
|
643
|
+
`runtimeNamespace` and `controller`), and which local members are attached, or
|
|
644
|
+
whether remote members are reachable through a tunnel, is the receiver's own
|
|
645
|
+
fact; the mesh grants nothing beyond the pairs above. A projection names each
|
|
646
|
+
network id once and only for the organization that owns it, so the key's
|
|
647
|
+
organization never separates two meshes of one validated projection; it is kept
|
|
648
|
+
so that the grouping holds without that rule.
|
|
649
|
+
|
|
630
650
|
`composeRuntimeNetworkProjectionDnsViews` builds one union view per local
|
|
631
651
|
workload source address, with every authorized FQDN and complete ready-replica A
|
|
632
652
|
set. An empty array declares NODATA; absent/unauthorized names remain NXDOMAIN,
|
|
@@ -1305,6 +1325,14 @@ and never content, and a controller and a node cannot disagree about the referen
|
|
|
1305
1325
|
absence: an assignment that states no endpoint is different content, and so a different digest, from
|
|
1306
1326
|
one that does.
|
|
1307
1327
|
|
|
1328
|
+
`designation` is the optional role its cluster gives the attempt's service, from
|
|
1329
|
+
`data.runtimeAssignmentDesignations`: `cluster-ingress` for the service Cloudly lists as the cluster's
|
|
1330
|
+
designated ingress (and delivers first). A node begins such a run before every other run it holds,
|
|
1331
|
+
by this statement rather than by delivery order. It is part of the attempt, so every later revision
|
|
1332
|
+
states the same value, and it grants nothing. A reader built before the member refuses an assignment
|
|
1333
|
+
that states it, so a controller states it only on a session whose offer speaks the release that added
|
|
1334
|
+
it or a later one.
|
|
1335
|
+
|
|
1308
1336
|
All fields except the digest itself participate in a domain-separated
|
|
1309
1337
|
SHA-256 digest. Digests provide integrity and compare-and-swap identity;
|
|
1310
1338
|
authentication must separately establish the trusted controller/node scope.
|
|
@@ -1564,6 +1592,15 @@ answer, more than a second beyond Cloudly's budget and below TypedSocket's 10 s
|
|
|
1564
1592
|
connection-restore deadline); and a relay's `relaySessionForwardMs` (8 s) for a
|
|
1565
1593
|
session read or report forwarded over its established Cloudly link.
|
|
1566
1594
|
|
|
1595
|
+
`data.runtimeSessionPushBounds.maximumInFlightPerNode` (8) is the other direction: how many requests a
|
|
1596
|
+
controller holds outstanding on one node's runtime session at once, every method and lane together
|
|
1597
|
+
(assignments, local storage claims, the network signing authority, projection and DNS lease renewals,
|
|
1598
|
+
stats reads), counted per node rather than per relay peer. A node holds that many beside the answers to
|
|
1599
|
+
its own requests and their cancellations without closing its session, and answers each within the
|
|
1600
|
+
relay's forward timeout for a push to a node (coreflow's `nodeForwardMs`, 8 s; not
|
|
1601
|
+
`runtimeSessionRequestBounds.relaySessionForwardMs`, which bounds the node's own requests to the
|
|
1602
|
+
controller); a controller that sends more may lose the session.
|
|
1603
|
+
|
|
1567
1604
|
### Assignment-Bound Runtime Secrets
|
|
1568
1605
|
|
|
1569
1606
|
`@serve.zone/interfaces/runtime` exports the Pallet assignment-secret contract.
|
|
@@ -3511,7 +3548,9 @@ checks the hostname's source profile stays the only way in whether or not the no
|
|
|
3511
3548
|
reachable from elsewhere. `validateClusterRouteSourceAllowList(value, path?)` judges one list. A
|
|
3512
3549
|
reader built before the member refuses the whole table that carries it, so an ingress that applies
|
|
3513
3550
|
it registers with `clusterIngressCapabilities.routeSourceAllowList` (`route-source-allowlist`), and
|
|
3514
|
-
Cloudly sends the member only to such an ingress through a relay that reads it.
|
|
3551
|
+
Cloudly sends the member only to such an ingress through a relay that reads it. An ingress that
|
|
3552
|
+
reads the registration refusal `controller-deadline` registers with
|
|
3553
|
+
`clusterIngressCapabilities.controllerDeadlineRefusal` (`controller-deadline-refusal`).
|
|
3515
3554
|
`IClusterPortRoute` forwards a TCP or UDP listen port without terminating anything. `validateClusterRouteTable(value, path?)` returns one
|
|
3516
3555
|
named reason per violation, refuses two routes for one hostname and two port routes for one
|
|
3517
3556
|
transport and port, and — because a validation reason travels into logs — **never quotes
|
|
@@ -3565,8 +3604,15 @@ the order Cloudly judges them: `not-ready` (Cloudly cannot judge registrations y
|
|
|
3565
3604
|
not the connection Cloudly elected for its cluster), `ingress-not-designated`, `node-mismatch` (the
|
|
3566
3605
|
node is not one the designation lets the ingress run on), `assignment-not-running` (no ready
|
|
3567
3606
|
assignment of the ingress service runs on that node under its current session, or it does not carry
|
|
3568
|
-
the designation's current credential), `bearer-refused
|
|
3569
|
-
or session changed while the registration was judged)
|
|
3607
|
+
the designation's current credential), `bearer-refused`, `conflict` (the designation, assignment
|
|
3608
|
+
or session changed while the registration was judged) and `controller-deadline` (Cloudly did not
|
|
3609
|
+
settle the registration within its request budget, `runtimeSessionRequestBounds.controllerBudgetMs`;
|
|
3610
|
+
a momentary state of Cloudly's, so the ingress registers again soon, and a registration whose commit
|
|
3611
|
+
the deadline interrupted is answered as its replay). A reader built before `controller-deadline`
|
|
3612
|
+
reads such a refusal as no named refusal, so Cloudly sends it only to an ingress whose registration
|
|
3613
|
+
states `clusterIngressCapabilities.controllerDeadlineRefusal` (`controller-deadline-refusal`) and
|
|
3614
|
+
answers `not-ready` to every other; the relay forwards a refusal untouched and never reads its
|
|
3615
|
+
reason. `retryAfterMs` is how long the ingress waits
|
|
3570
3616
|
before it registers again, 1 s to 300 s (`clusterIngressRegistrationContract`).
|
|
3571
3617
|
`validateClusterIngressRegistrationRefusal` names every violation and
|
|
3572
3618
|
`readClusterIngressRegistrationRefusal(errorData)` answers the refusal or `null`, reading plain data
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -44,9 +44,15 @@ export const clusterIngressLimits = Object.freeze({
|
|
|
44
44
|
* `routeSourceAllowList`: the ingress applies `IClusterHttpRoute.sourceAllowList`. A route table
|
|
45
45
|
* that carries the member is refused whole by a reader built before it, so Cloudly sends a route
|
|
46
46
|
* with a source allowlist only to an ingress that registered with this capability.
|
|
47
|
+
*
|
|
48
|
+
* `controllerDeadlineRefusal`: the ingress reads the registration refusal `controller-deadline`
|
|
49
|
+
* (`clusterIngressRegistrationRefusalReasons`) as a named refusal and waits its `retryAfterMs`. A
|
|
50
|
+
* reader built before the reason reads such a refusal as no named refusal, so Cloudly answers it
|
|
51
|
+
* only to a registration that states this capability and answers `not-ready` to every other.
|
|
47
52
|
*/
|
|
48
53
|
export const clusterIngressCapabilities = Object.freeze({
|
|
49
54
|
routeSourceAllowList: 'route-source-allowlist',
|
|
55
|
+
controllerDeadlineRefusal: 'controller-deadline-refusal',
|
|
50
56
|
} as const);
|
|
51
57
|
|
|
52
58
|
export type TClusterIngressCapability =
|
|
@@ -618,6 +624,14 @@ export const clusterIngressRegistrationRefusalReasons = Object.freeze([
|
|
|
618
624
|
'bearer-refused',
|
|
619
625
|
/** The designation, the assignment or the session changed while the registration was judged. */
|
|
620
626
|
'conflict',
|
|
627
|
+
/**
|
|
628
|
+
* Cloudly did not settle the registration within its request budget
|
|
629
|
+
* (`runtimeSessionRequestBounds.controllerBudgetMs`): a momentary state of Cloudly's, never a
|
|
630
|
+
* verdict on the ingress, which registers again soon. A registration whose commit the deadline
|
|
631
|
+
* interrupted may have been accepted; registering again is answered as its replay. Sent only to
|
|
632
|
+
* an ingress that registered with `clusterIngressCapabilities.controllerDeadlineRefusal`.
|
|
633
|
+
*/
|
|
634
|
+
'controller-deadline',
|
|
621
635
|
] as const);
|
|
622
636
|
|
|
623
637
|
export type TClusterIngressRegistrationRefusalReason = typeof clusterIngressRegistrationRefusalReasons[number];
|
package/ts/data/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ export * from './networklease.js';
|
|
|
46
46
|
export * from './networknode.js';
|
|
47
47
|
export * from './networkprojection.js';
|
|
48
48
|
export type { IRuntimeNetworkRouterPacketGrant, IRuntimeNetworkRouterProjection } from './networkrouter.js';
|
|
49
|
+
export type { IRuntimeNetworkPrivateMesh } from './networkprivatemesh.js';
|
|
49
50
|
export type { IRuntimeNetworkHostIngress, IRuntimeNetworkHostPacketGrant, IRuntimeNetworkHostProjection } from './networkhost.js';
|
|
50
51
|
export { runtimeNetworkHostContract } from './networkhost.js';
|
|
51
52
|
export type { IRuntimeNetworkWorkloadIngress, IRuntimeNetworkWorkloadIngressPacketGrant,
|
|
@@ -67,6 +68,7 @@ export * from './runtimestorageclaim.js';
|
|
|
67
68
|
export * from './runtimelocalstorageclaim.js';
|
|
68
69
|
export * from './runtimelocalvolumearchive.js';
|
|
69
70
|
export * from './runtimesession.js';
|
|
71
|
+
export * from './runtimesessionpush.js';
|
|
70
72
|
export * from './runtimesessionrefusal.js';
|
|
71
73
|
export * from './runtimeterminalreceipt.js';
|
|
72
74
|
export * from './secret.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { IRuntimeAuthorityReference } from './runtimeassignment.js';
|
|
2
|
+
import type { IServicePrivateNetworks } from './privatenetwork.js';
|
|
3
|
+
import type { IRuntimeNetworkEndpoint } from './networkprojection.js';
|
|
4
|
+
import { networkCanonical, requireNetwork } from '../private/networkcontract.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One private network of one organization and every projection endpoint, local or remote, whose
|
|
8
|
+
* service membership attaches it. Any two members on different nodes may reach each other, and no
|
|
9
|
+
* other workload may: this is the grouping the projection's workload packet grants are made of.
|
|
10
|
+
*/
|
|
11
|
+
export interface IRuntimeNetworkPrivateMesh {
|
|
12
|
+
organizationId: string;
|
|
13
|
+
networkId: string;
|
|
14
|
+
/** The lease references of the members, sorted by lease id; never empty. */
|
|
15
|
+
members: IRuntimeAuthorityReference[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ownerKey = (value: { organizationId: string; serviceId: string }): string => networkCanonical([value.organizationId, value.serviceId]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal grouping after capture. The public helper validates the whole projection. Keyed by the
|
|
22
|
+
* membership's organization and the attached network id, sorted by both, with members sorted by
|
|
23
|
+
* lease id; a network no endpoint's membership attaches has no mesh.
|
|
24
|
+
*/
|
|
25
|
+
export const privateMeshEndpointsOf = (projection: {
|
|
26
|
+
memberships: IServicePrivateNetworks[];
|
|
27
|
+
endpoints: IRuntimeNetworkEndpoint[];
|
|
28
|
+
}): Array<{ organizationId: string; networkId: string; members: IRuntimeNetworkEndpoint[] }> => {
|
|
29
|
+
const memberships = new Map(projection.memberships.map(member => [ownerKey(member), member]));
|
|
30
|
+
const meshes = new Map<string, { organizationId: string; networkId: string; members: IRuntimeNetworkEndpoint[] }>();
|
|
31
|
+
for (const endpoint of projection.endpoints) {
|
|
32
|
+
const member = memberships.get(ownerKey(endpoint.lease)); requireNetwork(member);
|
|
33
|
+
for (const attachment of member.attachments) {
|
|
34
|
+
const key = networkCanonical([member.organizationId, attachment.networkId]);
|
|
35
|
+
let mesh = meshes.get(key);
|
|
36
|
+
if (!mesh) meshes.set(key, mesh = { organizationId: member.organizationId, networkId: attachment.networkId, members: [] });
|
|
37
|
+
mesh.members.push(endpoint);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const byId = (left: IRuntimeNetworkEndpoint, right: IRuntimeNetworkEndpoint) =>
|
|
41
|
+
left.lease.id < right.lease.id ? -1 : left.lease.id > right.lease.id ? 1 : 0;
|
|
42
|
+
return [...meshes.values()].sort((left, right) => left.organizationId < right.organizationId ? -1
|
|
43
|
+
: left.organizationId > right.organizationId ? 1 : left.networkId < right.networkId ? -1 : left.networkId > right.networkId ? 1 : 0)
|
|
44
|
+
.map(mesh => ({ ...mesh, members: mesh.members.sort(byId) }));
|
|
45
|
+
};
|
|
@@ -9,6 +9,7 @@ import { routerPacketGrantsOf, snapshotRouterProjection,
|
|
|
9
9
|
type IRuntimeNetworkRouterPacketGrant, type IRuntimeNetworkRouterProjection } from './networkrouter.js';
|
|
10
10
|
import { hostPacketGrantsOf, snapshotHostProjection,
|
|
11
11
|
type IRuntimeNetworkHostPacketGrant, type IRuntimeNetworkHostProjection } from './networkhost.js';
|
|
12
|
+
import { privateMeshEndpointsOf, type IRuntimeNetworkPrivateMesh } from './networkprivatemesh.js';
|
|
12
13
|
import { snapshotWorkloadIngressProjection, workloadIngressPacketGrantsOf,
|
|
13
14
|
type IRuntimeNetworkWorkloadIngressPacketGrant, type IRuntimeNetworkWorkloadIngressProjection } from './networkworkloadingress.js';
|
|
14
15
|
import { requireFirstProjectionRetirement, requireProjectionRetirement, snapshotRuntimeNetworkProjectionRetirement,
|
|
@@ -601,23 +602,28 @@ export const snapshotRuntimeNetworkProjection = (value: unknown): IRuntimeNetwor
|
|
|
601
602
|
retiredLeases: sortedReferences(item.retiredLeases), retiredHandoffs: sortedReferences(item.retiredHandoffs), digest: networkDigest(item.digest) };
|
|
602
603
|
});
|
|
603
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Workload grants are the ordered pairs of distinct members of each private mesh with at least one
|
|
607
|
+
* end on this projection's node, each pair once however many networks its ends share; egress grants
|
|
608
|
+
* follow per local source. At most `maximumGrants` in all.
|
|
609
|
+
*/
|
|
604
610
|
const grantsOf = (projection: IRuntimeNetworkProjection): IRuntimeNetworkPacketGrant[] => {
|
|
605
611
|
const result: IRuntimeNetworkPacketGrant[] = [];
|
|
606
|
-
const memberships = new Map(projection.memberships.map(member => [ownerKey(member), member]));
|
|
607
612
|
const append = (grant: IRuntimeNetworkPacketGrant) => {
|
|
608
613
|
requireNetwork(result.length < 4096); result.push(grant);
|
|
609
614
|
};
|
|
615
|
+
const local = new Set(projection.endpoints.filter(endpoint => networkSameScope(endpoint.lease, projection)));
|
|
616
|
+
const granted = new Map<IRuntimeNetworkEndpoint, Set<IRuntimeNetworkEndpoint>>();
|
|
617
|
+
for (const mesh of privateMeshEndpointsOf(projection)) for (const source of mesh.members) for (const destination of mesh.members) {
|
|
618
|
+
if (source === destination || (!local.has(source) && !local.has(destination))) continue;
|
|
619
|
+
let reached = granted.get(source);
|
|
620
|
+
if (!reached) granted.set(source, reached = new Set());
|
|
621
|
+
if (reached.has(destination)) continue;
|
|
622
|
+
reached.add(destination);
|
|
623
|
+
append({ source: networkReferenceOf(source.lease), destination: { kind: 'workload', lease: networkReferenceOf(destination.lease) } });
|
|
624
|
+
}
|
|
610
625
|
for (const source of projection.endpoints) {
|
|
611
|
-
|
|
612
|
-
for (const destination of projection.endpoints) {
|
|
613
|
-
if (source === destination || (!networkSameScope(source.lease, projection) && !networkSameScope(destination.lease, projection))) continue;
|
|
614
|
-
const target = memberships.get(ownerKey(destination.lease)); requireNetwork(target);
|
|
615
|
-
if (member.organizationId === target.organizationId && member.attachments.some(attachment =>
|
|
616
|
-
target.attachments.some(other => other.networkId === attachment.networkId))) {
|
|
617
|
-
append({ source: networkReferenceOf(source.lease), destination: { kind: 'workload', lease: networkReferenceOf(destination.lease) } });
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (!networkSameScope(source.lease, projection)) continue;
|
|
626
|
+
if (!local.has(source)) continue;
|
|
621
627
|
if (source.publicEgress) append({ source: networkReferenceOf(source.lease), destination: { kind: 'public' } });
|
|
622
628
|
for (const id of source.platformEndpointIds) {
|
|
623
629
|
const platform = projection.protectedAuthority.platformEndpoints.find(item => item.id === id); requireNetwork(platform);
|
|
@@ -726,6 +732,16 @@ export const validateRuntimeNetworkProjection = async (value: unknown): Promise<
|
|
|
726
732
|
export const getRuntimeNetworkProjectionPacketGrants = async (value: unknown): Promise<IRuntimeNetworkPacketGrant[]> =>
|
|
727
733
|
grantsOf(await validateRuntimeNetworkProjection(value));
|
|
728
734
|
|
|
735
|
+
/**
|
|
736
|
+
* Every private network of every organization with the projection endpoints, local or remote, whose
|
|
737
|
+
* membership attaches it (`IRuntimeNetworkPrivateMesh`): the grouping the workload packet grants
|
|
738
|
+
* are made of, sorted by organization and network id. Which members a node realizes is its own
|
|
739
|
+
* decision; who may reach whom is this one.
|
|
740
|
+
*/
|
|
741
|
+
export const getRuntimeNetworkProjectionPrivateMeshes = async (value: unknown): Promise<IRuntimeNetworkPrivateMesh[]> =>
|
|
742
|
+
privateMeshEndpointsOf(await validateRuntimeNetworkProjection(value)).map(mesh => ({ organizationId: mesh.organizationId,
|
|
743
|
+
networkId: mesh.networkId, members: mesh.members.map(member => networkReferenceOf(member.lease)) }));
|
|
744
|
+
|
|
729
745
|
/** Router-origin authority comes only from explicit signed selections, never DNS names or workload grants. */
|
|
730
746
|
export const getRuntimeNetworkProjectionRouterPacketGrants = async (value: unknown): Promise<IRuntimeNetworkRouterPacketGrant[]> =>
|
|
731
747
|
routerPacketGrantsOf(await validateRuntimeNetworkProjection(value));
|
|
@@ -39,6 +39,19 @@ export type TRuntimeAssignmentDisposition =
|
|
|
39
39
|
| 'stop-preserve'
|
|
40
40
|
| 'remove-runtime-preserve-storage';
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* What a cluster designates the service of an attempt as, stated by the controller so a node can
|
|
44
|
+
* order its work by the service's role instead of by the order the deliveries arrived in.
|
|
45
|
+
*
|
|
46
|
+
* `cluster-ingress`: the service is its cluster's designated ingress, the service Cloudly lists as
|
|
47
|
+
* designated (`IClusterIngressDesignation` for a cluster born on Pallet,
|
|
48
|
+
* `IFleetCutoverIngressDesignation` after a Swarm cutover) and delivers first, because a cluster
|
|
49
|
+
* serves nothing until its ingress runs. A node begins such a run before every other run it holds.
|
|
50
|
+
*/
|
|
51
|
+
export const runtimeAssignmentDesignations = ['cluster-ingress'] as const;
|
|
52
|
+
|
|
53
|
+
export type TRuntimeAssignmentDesignation = (typeof runtimeAssignmentDesignations)[number];
|
|
54
|
+
|
|
42
55
|
export interface IRuntimeAssignment extends IRuntimeAssignmentScope {
|
|
43
56
|
assignmentId: string;
|
|
44
57
|
serviceId: string;
|
|
@@ -55,6 +68,19 @@ export interface IRuntimeAssignment extends IRuntimeAssignmentScope {
|
|
|
55
68
|
disposition: TRuntimeAssignmentDisposition;
|
|
56
69
|
/** No automatic relocation or expiry-based claim of a stopped writer. */
|
|
57
70
|
offlinePolicy: 'continue-node-bound';
|
|
71
|
+
/**
|
|
72
|
+
* The cluster's designation of this attempt's service (`runtimeAssignmentDesignations`); absent
|
|
73
|
+
* for a service the cluster designates nothing. It is part of the attempt, as every member but
|
|
74
|
+
* `generation`, `previous`, `disposition` and `digest` is: every later revision of the attempt
|
|
75
|
+
* states the same value, so a designation that moves to another service reaches the nodes with
|
|
76
|
+
* the services' next attempts. It orders a node's work and grants nothing: no authority,
|
|
77
|
+
* placement, credential or network access follows from it.
|
|
78
|
+
*
|
|
79
|
+
* A reader built before this member refuses an assignment that states it (exact keys), so a
|
|
80
|
+
* controller states it only on a session whose offer speaks the release that added it or a later
|
|
81
|
+
* one, and states nothing on any other.
|
|
82
|
+
*/
|
|
83
|
+
designation?: TRuntimeAssignmentDesignation;
|
|
58
84
|
workload: {
|
|
59
85
|
/**
|
|
60
86
|
* The registry that published this image, which is the publication identity the image release
|
|
@@ -320,15 +346,21 @@ const assignmentKeys = [
|
|
|
320
346
|
'assignmentId', 'serviceId', 'replicaId', 'attemptId', 'workerGroupId', 'siteId',
|
|
321
347
|
'generation', 'previous', 'disposition', 'offlinePolicy', 'workload', 'digest',
|
|
322
348
|
] as const;
|
|
349
|
+
/** Stated only for a service its cluster designates (`runtimeAssignmentDesignations`). */
|
|
350
|
+
const optionalAssignmentKeys = ['designation'] as const;
|
|
323
351
|
|
|
324
352
|
const assertAssignment = (valueArg: unknown): IRuntimeAssignment => {
|
|
325
|
-
if (!isExactRecord(valueArg, assignmentKeys)
|
|
353
|
+
if (!isExactRecord(valueArg, assignmentKeys, optionalAssignmentKeys)
|
|
326
354
|
|| !isController(valueArg.controller)
|
|
327
355
|
|| !isPositiveInteger(valueArg.generation) || !isDigest(valueArg.digest)
|
|
328
356
|
|| valueArg.offlinePolicy !== 'continue-node-bound'
|
|
329
357
|
|| !['run', 'stop-preserve', 'remove-runtime-preserve-storage'].includes(
|
|
330
358
|
valueArg.disposition as string,
|
|
331
359
|
)) fail('runtime assignment must use its exact schema');
|
|
360
|
+
if (Object.hasOwn(valueArg, 'designation')
|
|
361
|
+
&& !runtimeAssignmentDesignations.some((designationArg) => designationArg === valueArg.designation)) {
|
|
362
|
+
fail('runtime assignment designation must be a stated designation');
|
|
363
|
+
}
|
|
332
364
|
for (const key of ['organizationId', 'runtimeNamespace', 'assignmentId', 'serviceId', 'attemptId']) {
|
|
333
365
|
if (!isIdentifier(valueArg[key])) fail(`runtime assignment ${key} must be a canonical identifier`);
|
|
334
366
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many requests a controller holds outstanding on one node's runtime session at once.
|
|
3
|
+
*
|
|
4
|
+
* Counted per node, never per lane, method or carrier: every request a controller sends a node over
|
|
5
|
+
* its runtime session counts — `applyRuntimeAssignment`, `applyRuntimeLocalStorageClaim`,
|
|
6
|
+
* `applyRuntimeNetworkSigningAuthority`, `applyRuntimeNetworkProjection`,
|
|
7
|
+
* `applyRuntimeNetworkDnsLeaseRenewal` and `readRuntimeAssignmentStats` together, whatever pass or
|
|
8
|
+
* lane of the controller sends it — from the moment it is sent until its answer or its failure
|
|
9
|
+
* arrived. One relay peer carries a whole cluster, so the count is each node's own, not the peer's.
|
|
10
|
+
* A relay adds no request of its own while the controller's session is up (its DNS lease escrow
|
|
11
|
+
* offers one renewal per node, and only while that session is down), so it stays within the same
|
|
12
|
+
* count.
|
|
13
|
+
*
|
|
14
|
+
* A node holds this many at once beside the answers to its own requests and the cancellation each
|
|
15
|
+
* of them may draw, and never closes its session over them; it answers each within the relay's
|
|
16
|
+
* forward timeout for a push to a node (coreflow's `nodeForwardMs`, 8 s; not
|
|
17
|
+
* `runtimeSessionRequestBounds.relaySessionForwardMs`, which bounds the node's own requests to the
|
|
18
|
+
* controller), so a request the controller stopped waiting for is no longer held on the node. A
|
|
19
|
+
* controller that sends more than this may lose the node's session: the node's transport bounds
|
|
20
|
+
* what it queues and closes the connection past that bound.
|
|
21
|
+
*/
|
|
22
|
+
export const runtimeSessionPushBounds = Object.freeze({
|
|
23
|
+
maximumInFlightPerNode: 8,
|
|
24
|
+
} as const);
|