@telorun/k8s-runner 0.14.0 → 0.15.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/README.md +93 -14
- package/dist/config.d.ts +44 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +51 -4
- package/dist/config.js.map +1 -1
- package/dist/k8s/backend.d.ts +4 -0
- package/dist/k8s/backend.d.ts.map +1 -1
- package/dist/k8s/backend.js +55 -17
- package/dist/k8s/backend.js.map +1 -1
- package/dist/k8s/client.d.ts +7 -1
- package/dist/k8s/client.d.ts.map +1 -1
- package/dist/k8s/client.js +3 -1
- package/dist/k8s/client.js.map +1 -1
- package/dist/k8s/pod-status.d.ts +5 -0
- package/dist/k8s/pod-status.d.ts.map +1 -1
- package/dist/k8s/pod-status.js +7 -0
- package/dist/k8s/pod-status.js.map +1 -1
- package/dist/k8s/routing/create-or-replace.d.ts +5 -0
- package/dist/k8s/routing/create-or-replace.d.ts.map +1 -0
- package/dist/k8s/routing/create-or-replace.js +15 -0
- package/dist/k8s/routing/create-or-replace.js.map +1 -0
- package/dist/k8s/routing/gateway-router.d.ts +5 -0
- package/dist/k8s/routing/gateway-router.d.ts.map +1 -0
- package/dist/k8s/routing/gateway-router.js +178 -0
- package/dist/k8s/routing/gateway-router.js.map +1 -0
- package/dist/k8s/routing/index.d.ts +24 -0
- package/dist/k8s/routing/index.d.ts.map +1 -0
- package/dist/k8s/routing/index.js +27 -0
- package/dist/k8s/routing/index.js.map +1 -0
- package/dist/k8s/routing/ingress-router.d.ts +13 -0
- package/dist/k8s/routing/ingress-router.d.ts.map +1 -0
- package/dist/k8s/routing/ingress-router.js +125 -0
- package/dist/k8s/routing/ingress-router.js.map +1 -0
- package/dist/k8s/routing/route-health.d.ts +34 -0
- package/dist/k8s/routing/route-health.d.ts.map +1 -0
- package/dist/k8s/routing/route-health.js +77 -0
- package/dist/k8s/routing/route-health.js.map +1 -0
- package/dist/k8s/routing/routing-mode.d.ts +45 -0
- package/dist/k8s/routing/routing-mode.d.ts.map +1 -0
- package/dist/k8s/routing/routing-mode.js +141 -0
- package/dist/k8s/routing/routing-mode.js.map +1 -0
- package/dist/k8s/routing/session-endpoints.d.ts +25 -0
- package/dist/k8s/routing/session-endpoints.d.ts.map +1 -0
- package/dist/k8s/routing/session-endpoints.js +36 -0
- package/dist/k8s/routing/session-endpoints.js.map +1 -0
- package/dist/k8s/routing/session-router.d.ts +57 -0
- package/dist/k8s/routing/session-router.d.ts.map +1 -0
- package/dist/k8s/routing/session-router.js +2 -0
- package/dist/k8s/routing/session-router.js.map +1 -0
- package/dist/k8s/routing/session-service.d.ts +14 -0
- package/dist/k8s/routing/session-service.d.ts.map +1 -0
- package/dist/k8s/routing/session-service.js +42 -0
- package/dist/k8s/routing/session-service.js.map +1 -0
- package/dist/k8s/watch-session.d.ts +3 -0
- package/dist/k8s/watch-session.d.ts.map +1 -1
- package/dist/k8s/watch-session.js +82 -75
- package/dist/k8s/watch-session.js.map +1 -1
- package/dist/k8s/workspace-configmap.d.ts.map +1 -1
- package/dist/k8s/workspace-configmap.js +3 -12
- package/dist/k8s/workspace-configmap.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +34 -1
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +116 -13
- package/src/k8s/backend.ts +73 -23
- package/src/k8s/client.ts +17 -1
- package/src/k8s/pod-status.ts +8 -0
- package/src/k8s/routing/create-or-replace.ts +16 -0
- package/src/k8s/routing/gateway-router.test.ts +236 -0
- package/src/k8s/routing/gateway-router.ts +219 -0
- package/src/k8s/routing/index.ts +44 -0
- package/src/k8s/routing/ingress-router.test.ts +236 -0
- package/src/k8s/routing/ingress-router.ts +157 -0
- package/src/k8s/routing/route-health.test.ts +215 -0
- package/src/k8s/routing/route-health.ts +95 -0
- package/src/k8s/routing/routing-mode.test.ts +218 -0
- package/src/k8s/routing/routing-mode.ts +191 -0
- package/src/k8s/routing/session-endpoints.ts +45 -0
- package/src/k8s/routing/session-router.ts +59 -0
- package/src/k8s/routing/session-service.ts +55 -0
- package/src/k8s/watch-session.ts +82 -64
- package/src/k8s/workspace-configmap.ts +3 -15
- package/src/server.ts +38 -1
- package/dist/k8s/ingress.d.ts +0 -15
- package/dist/k8s/ingress.d.ts.map +0 -1
- package/dist/k8s/ingress.js +0 -102
- package/dist/k8s/ingress.js.map +0 -1
- package/src/k8s/ingress.test.ts +0 -146
- package/src/k8s/ingress.ts +0 -127
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pod-status.d.ts","sourceRoot":"","sources":["../../src/k8s/pod-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuC,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;GAKG;AAEH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,SAAS,CAEnE;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEzD;AAED;sFACsF;AACtF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAYjE;AAgBD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,GAAG,SAAS,CAK5E;AAGD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG7D;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOtD;AAyCD,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzF;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE3C;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQxC"}
|
|
1
|
+
{"version":3,"file":"pod-status.d.ts","sourceRoot":"","sources":["../../src/k8s/pod-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuC,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;GAKG;AAEH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,SAAS,CAEnE;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEzD;AAED;sFACsF;AACtF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAYjE;AAgBD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,GAAG,SAAS,CAK5E;AAGD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG7D;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOtD;AAyCD,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzF;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE3C;AAED;;;4CAG4C;AAC5C,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhD;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQxC"}
|
package/dist/k8s/pod-status.js
CHANGED
|
@@ -122,6 +122,13 @@ export async function deletePod(kube, ns, name) {
|
|
|
122
122
|
export function is404(err) {
|
|
123
123
|
return statusCode(err) === 404;
|
|
124
124
|
}
|
|
125
|
+
/** A name-already-taken rejection — what makes a create idempotent by falling
|
|
126
|
+
* through to a replace. Reads the status through `statusCode` like every other
|
|
127
|
+
* classification here, so an `ECONNREFUSED` or a `DOMException`'s legacy number
|
|
128
|
+
* in `code` cannot be mistaken for one. */
|
|
129
|
+
export function isConflict(err) {
|
|
130
|
+
return statusCode(err) === 409;
|
|
131
|
+
}
|
|
125
132
|
export function msg(err) {
|
|
126
133
|
if (err instanceof Error)
|
|
127
134
|
return err.message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pod-status.js","sourceRoot":"","sources":["../../src/k8s/pod-status.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pod-status.js","sourceRoot":"","sources":["../../src/k8s/pod-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** A create that yields to an existing object of the same name. Every routing
|
|
2
|
+
* object is republished on reload (an app's declared port set changes), so the
|
|
3
|
+
* write has to be idempotent rather than create-once. */
|
|
4
|
+
export declare function createOrReplace(create: () => Promise<unknown>, replace: () => Promise<unknown>): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=create-or-replace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-or-replace.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/create-or-replace.ts"],"names":[],"mappings":"AAEA;;0DAE0D;AAC1D,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAOf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isConflict } from "../pod-status.js";
|
|
2
|
+
/** A create that yields to an existing object of the same name. Every routing
|
|
3
|
+
* object is republished on reload (an app's declared port set changes), so the
|
|
4
|
+
* write has to be idempotent rather than create-once. */
|
|
5
|
+
export async function createOrReplace(create, replace) {
|
|
6
|
+
try {
|
|
7
|
+
await create();
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (!isConflict(err))
|
|
11
|
+
throw err;
|
|
12
|
+
await replace();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=create-or-replace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-or-replace.js","sourceRoot":"","sources":["../../../src/k8s/routing/create-or-replace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;0DAE0D;AAC1D,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAA8B,EAC9B,OAA+B;IAE/B,IAAI,CAAC;QACH,MAAM,MAAM,EAAE,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,MAAM,GAAG,CAAC;QAChC,MAAM,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { K8sRunnerConfig } from "../../config.js";
|
|
2
|
+
import type { KubeClient } from "../client.js";
|
|
3
|
+
import type { SessionRouter } from "./session-router.js";
|
|
4
|
+
export declare function createGatewayRouter(kube: KubeClient, config: K8sRunnerConfig, apiVersion: string): SessionRouter;
|
|
5
|
+
//# sourceMappingURL=gateway-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-router.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/gateway-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK/C,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,qBAAqB,CAAC;AA8C7B,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,MAAM,GACjB,aAAa,CA6Jf"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { is404 } from "../pod-status.js";
|
|
2
|
+
import { createOrReplace } from "./create-or-replace.js";
|
|
3
|
+
import { hostForPort } from "./session-endpoints.js";
|
|
4
|
+
import { podOwnerRef, sessionObjectName } from "./session-service.js";
|
|
5
|
+
const GROUP = "gateway.networking.k8s.io";
|
|
6
|
+
const PLURAL = "httproutes";
|
|
7
|
+
/**
|
|
8
|
+
* ONE HTTPRoute PER PORT, and that is forced by the API rather than chosen:
|
|
9
|
+
* `hostnames` is a property of the whole HTTPRoute, while a `rule` selects on
|
|
10
|
+
* path/header/method — never on host. A single route carrying every session
|
|
11
|
+
* hostname plus one rule per port would therefore send every host to whichever
|
|
12
|
+
* rule matched `/` first, silently collapsing all of a session's ports onto one.
|
|
13
|
+
*
|
|
14
|
+
* An Ingress rule owns its host, which is why that layer needs only one object.
|
|
15
|
+
*/
|
|
16
|
+
function routeName(sessionId, port) {
|
|
17
|
+
return `${sessionObjectName(sessionId)}-${port}`;
|
|
18
|
+
}
|
|
19
|
+
function condition(parent, type) {
|
|
20
|
+
return parent.conditions?.find((c) => c.type === type);
|
|
21
|
+
}
|
|
22
|
+
/** Phrase a refusal in the controller's own words — every cause here is fixed in
|
|
23
|
+
* the cluster (a listener that does not admit this namespace, a hostname outside
|
|
24
|
+
* the listener's own), so the controller's `reason` is the actionable part. */
|
|
25
|
+
function refusal(c) {
|
|
26
|
+
const reason = c.reason ?? "rejected";
|
|
27
|
+
return c.message ? `${reason}: ${c.message}` : reason;
|
|
28
|
+
}
|
|
29
|
+
export function createGatewayRouter(kube, config, apiVersion) {
|
|
30
|
+
const ns = config.sessionNamespace;
|
|
31
|
+
const gateway = config.sessionRouting.gateway;
|
|
32
|
+
if (!gateway) {
|
|
33
|
+
// Unreachable through the factory, which refuses this at boot; kept so the
|
|
34
|
+
// invariant is stated where the field is read rather than assumed.
|
|
35
|
+
throw new Error("gateway routing selected without a configured Gateway parent");
|
|
36
|
+
}
|
|
37
|
+
const base = { group: GROUP, version: apiVersion, namespace: ns, plural: PLURAL };
|
|
38
|
+
// Arrow consts, not declarations: a hoisted `function` resets the narrowing of
|
|
39
|
+
// `gateway` above, since TypeScript cannot prove it is not called earlier.
|
|
40
|
+
const buildRoute = (args, port) => {
|
|
41
|
+
return {
|
|
42
|
+
apiVersion: `${GROUP}/${apiVersion}`,
|
|
43
|
+
kind: "HTTPRoute",
|
|
44
|
+
metadata: {
|
|
45
|
+
name: routeName(args.sessionId, port),
|
|
46
|
+
namespace: ns,
|
|
47
|
+
labels: {
|
|
48
|
+
"app.kubernetes.io/managed-by": config.managedByLabel,
|
|
49
|
+
"telo.run/session-id": args.sessionId,
|
|
50
|
+
},
|
|
51
|
+
ownerReferences: [podOwnerRef(args.podName, args.podUid)],
|
|
52
|
+
},
|
|
53
|
+
spec: {
|
|
54
|
+
parentRefs: [
|
|
55
|
+
{
|
|
56
|
+
group: GROUP,
|
|
57
|
+
kind: "Gateway",
|
|
58
|
+
name: gateway.name,
|
|
59
|
+
namespace: gateway.namespace,
|
|
60
|
+
...(gateway.sectionName ? { sectionName: gateway.sectionName } : {}),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
hostnames: [hostForPort(config, args.sessionId, port)],
|
|
64
|
+
rules: [
|
|
65
|
+
{
|
|
66
|
+
matches: [{ path: { type: "PathPrefix", value: "/" } }],
|
|
67
|
+
backendRefs: [{ name: args.serviceName, port }],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const listSessionRoutes = async (sessionId) => {
|
|
74
|
+
const list = (await kube.custom.listNamespacedCustomObject({
|
|
75
|
+
...base,
|
|
76
|
+
labelSelector: `telo.run/session-id=${sessionId}`,
|
|
77
|
+
}));
|
|
78
|
+
return list.items ?? [];
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
layer: "gateway",
|
|
82
|
+
async publish(args) {
|
|
83
|
+
const tcp = args.ports.filter((p) => p.protocol === "tcp");
|
|
84
|
+
const desired = new Set(tcp.map((p) => routeName(args.sessionId, p.port)));
|
|
85
|
+
for (const p of tcp) {
|
|
86
|
+
const body = buildRoute(args, p.port);
|
|
87
|
+
const name = routeName(args.sessionId, p.port);
|
|
88
|
+
await createOrReplace(() => kube.custom.createNamespacedCustomObject({ ...base, body }), async () => {
|
|
89
|
+
const existing = (await kube.custom.getNamespacedCustomObject({
|
|
90
|
+
...base,
|
|
91
|
+
name,
|
|
92
|
+
}));
|
|
93
|
+
await kube.custom.replaceNamespacedCustomObject({
|
|
94
|
+
...base,
|
|
95
|
+
name,
|
|
96
|
+
body: {
|
|
97
|
+
...body,
|
|
98
|
+
metadata: {
|
|
99
|
+
...body.metadata,
|
|
100
|
+
resourceVersion: existing.metadata?.resourceVersion,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// A reload that DROPS a port must delete that port's route. In ingress mode
|
|
107
|
+
// the single object is replaced and the stale rule vanishes with it; here
|
|
108
|
+
// each port owns an object, so a removed one would keep routing to a port
|
|
109
|
+
// nothing listens on.
|
|
110
|
+
for (const existing of await listSessionRoutes(args.sessionId)) {
|
|
111
|
+
const name = existing.metadata?.name;
|
|
112
|
+
if (!name || desired.has(name))
|
|
113
|
+
continue;
|
|
114
|
+
try {
|
|
115
|
+
await kube.custom.deleteNamespacedCustomObject({ ...base, name });
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (!is404(err))
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return tcp.map((p) => ({
|
|
123
|
+
host: hostForPort(config, args.sessionId, p.port),
|
|
124
|
+
port: p.port,
|
|
125
|
+
}));
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* Gateway API is the layer that can actually answer this: a controller writes
|
|
129
|
+
* `Accepted` and `ResolvedRefs` per parent, so a route refused because the
|
|
130
|
+
* Gateway's listener does not admit this namespace says exactly that
|
|
131
|
+
* (`NotAllowedByListeners`) instead of timing out with no cause.
|
|
132
|
+
*/
|
|
133
|
+
async verdictFor(sessionId, route) {
|
|
134
|
+
let obj;
|
|
135
|
+
try {
|
|
136
|
+
obj = (await kube.custom.getNamespacedCustomObject({
|
|
137
|
+
...base,
|
|
138
|
+
name: routeName(sessionId, route.port),
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (is404(err))
|
|
143
|
+
return { kind: "pending" };
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
const parents = obj.status?.parents ?? [];
|
|
147
|
+
if (parents.length === 0)
|
|
148
|
+
return { kind: "pending" };
|
|
149
|
+
let rejected;
|
|
150
|
+
for (const parent of parents) {
|
|
151
|
+
const accepted = condition(parent, "Accepted");
|
|
152
|
+
const resolved = condition(parent, "ResolvedRefs");
|
|
153
|
+
if (accepted?.status === "False") {
|
|
154
|
+
rejected ??= refusal(accepted);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (resolved?.status === "False") {
|
|
158
|
+
rejected ??= refusal(resolved);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// A route may attach to several parents; one that programmed it is enough
|
|
162
|
+
// for traffic to arrive, so a positive verdict wins over a sibling's refusal.
|
|
163
|
+
if (accepted?.status === "True" && resolved?.status !== "False") {
|
|
164
|
+
return { kind: "programmed" };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return rejected ? { kind: "rejected", reason: rejected } : { kind: "pending" };
|
|
168
|
+
},
|
|
169
|
+
unclaimedReason() {
|
|
170
|
+
const section = gateway.sectionName ? ` (listener '${gateway.sectionName}')` : "";
|
|
171
|
+
return (`no controller claimed the session HTTPRoute for Gateway ` +
|
|
172
|
+
`'${gateway.namespace}/${gateway.name}'${section}. Check the Gateway exists, is programmed, ` +
|
|
173
|
+
`and that a listener admits routes from namespace '${ns}' ` +
|
|
174
|
+
`(spec.listeners[].allowedRoutes.namespaces).`);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=gateway-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-router.js","sourceRoot":"","sources":["../../../src/k8s/routing/gateway-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { K8sRunnerConfig } from "../../config.js";
|
|
2
|
+
import type { KubeClient } from "../client.js";
|
|
3
|
+
import { type ResolvedRouting } from "./routing-mode.js";
|
|
4
|
+
import type { SessionRouter } from "./session-router.js";
|
|
5
|
+
export { createOrReplace } from "./create-or-replace.js";
|
|
6
|
+
export { buildSessionIngress } from "./ingress-router.js";
|
|
7
|
+
export { watchRouteHealth, type WatchRouteHealthOptions } from "./route-health.js";
|
|
8
|
+
export { detectRoutingSupport, resolveRouting, type ClusterRoutingSupport, type ResolvedRouting, } from "./routing-mode.js";
|
|
9
|
+
export { endpointsFor, hostForPort } from "./session-endpoints.js";
|
|
10
|
+
export type { PublishedRoute, PublishRouteArgs, RouteVerdict, SessionRouter, } from "./session-router.js";
|
|
11
|
+
export { buildSessionService, podOwnerRef, sessionObjectName } from "./session-service.js";
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the cluster's routing layer and build the router for it. Called ONCE at
|
|
14
|
+
* boot: an unresolvable configuration must fail the runner rather than every
|
|
15
|
+
* session it would otherwise accept and leave unroutable.
|
|
16
|
+
*
|
|
17
|
+
* `undefined` means logs-only — the honest answer when there is deliberately
|
|
18
|
+
* nothing to publish, distinct from a failure, which throws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createSessionRouter(kube: KubeClient, config: K8sRunnerConfig): Promise<{
|
|
21
|
+
router?: SessionRouter;
|
|
22
|
+
resolved: ResolvedRouting;
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACnE,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE3F;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,CAAC,CAOhE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createGatewayRouter } from "./gateway-router.js";
|
|
2
|
+
import { createIngressRouter } from "./ingress-router.js";
|
|
3
|
+
import { resolveRouting } from "./routing-mode.js";
|
|
4
|
+
export { createOrReplace } from "./create-or-replace.js";
|
|
5
|
+
export { buildSessionIngress } from "./ingress-router.js";
|
|
6
|
+
export { watchRouteHealth } from "./route-health.js";
|
|
7
|
+
export { detectRoutingSupport, resolveRouting, } from "./routing-mode.js";
|
|
8
|
+
export { endpointsFor, hostForPort } from "./session-endpoints.js";
|
|
9
|
+
export { buildSessionService, podOwnerRef, sessionObjectName } from "./session-service.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the cluster's routing layer and build the router for it. Called ONCE at
|
|
12
|
+
* boot: an unresolvable configuration must fail the runner rather than every
|
|
13
|
+
* session it would otherwise accept and leave unroutable.
|
|
14
|
+
*
|
|
15
|
+
* `undefined` means logs-only — the honest answer when there is deliberately
|
|
16
|
+
* nothing to publish, distinct from a failure, which throws.
|
|
17
|
+
*/
|
|
18
|
+
export async function createSessionRouter(kube, config) {
|
|
19
|
+
const resolved = await resolveRouting(kube, config);
|
|
20
|
+
if (resolved.layer === "none")
|
|
21
|
+
return { resolved };
|
|
22
|
+
if (resolved.layer === "gateway") {
|
|
23
|
+
return { router: createGatewayRouter(kube, config, resolved.apiVersion), resolved };
|
|
24
|
+
}
|
|
25
|
+
return { router: createIngressRouter(kube, config), resolved };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/k8s/routing/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAwB,MAAM,mBAAmB,CAAC;AAGzE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAgC,MAAM,mBAAmB,CAAC;AACnF,OAAO,EACL,oBAAoB,EACpB,cAAc,GAGf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAOnE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE3F;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAgB,EAChB,MAAuB;IAEvB,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpD,IAAI,QAAQ,CAAC,KAAK,KAAK,MAAM;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACnD,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { V1Ingress } from "@kubernetes/client-node";
|
|
2
|
+
import type { PortMapping } from "@telorun/runner-core";
|
|
3
|
+
import type { K8sRunnerConfig } from "../../config.js";
|
|
4
|
+
import type { KubeClient } from "../client.js";
|
|
5
|
+
import type { SessionRouter } from "./session-router.js";
|
|
6
|
+
/** One Ingress carries every host for a session: unlike Gateway API, an Ingress
|
|
7
|
+
* rule is scoped to its own host, so per-host backends fit in one object. */
|
|
8
|
+
export declare function buildSessionIngress(config: K8sRunnerConfig, sessionId: string, serviceName: string, podName: string, podUid: string, ports: PortMapping[]): {
|
|
9
|
+
ingress: V1Ingress;
|
|
10
|
+
hosts: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function createIngressRouter(kube: KubeClient, config: K8sRunnerConfig): SessionRouter;
|
|
13
|
+
//# sourceMappingURL=ingress-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingress-router.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/ingress-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK/C,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,qBAAqB,CAAC;AAE7B;8EAC8E;AAC9E,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,EAAE,GACnB;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAgDzC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,GAAG,aAAa,CAgF5F"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { is404 } from "../pod-status.js";
|
|
2
|
+
import { createOrReplace } from "./create-or-replace.js";
|
|
3
|
+
import { hostForPort } from "./session-endpoints.js";
|
|
4
|
+
import { podOwnerRef, sessionObjectName } from "./session-service.js";
|
|
5
|
+
/** One Ingress carries every host for a session: unlike Gateway API, an Ingress
|
|
6
|
+
* rule is scoped to its own host, so per-host backends fit in one object. */
|
|
7
|
+
export function buildSessionIngress(config, sessionId, serviceName, podName, podUid, ports) {
|
|
8
|
+
const rules = ports
|
|
9
|
+
.filter((p) => p.protocol === "tcp")
|
|
10
|
+
.map((p) => ({
|
|
11
|
+
host: hostForPort(config, sessionId, p.port),
|
|
12
|
+
http: {
|
|
13
|
+
paths: [
|
|
14
|
+
{
|
|
15
|
+
path: "/",
|
|
16
|
+
pathType: "Prefix",
|
|
17
|
+
backend: { service: { name: serviceName, port: { number: p.port } } },
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
const ingress = {
|
|
23
|
+
apiVersion: "networking.k8s.io/v1",
|
|
24
|
+
kind: "Ingress",
|
|
25
|
+
metadata: {
|
|
26
|
+
name: sessionObjectName(sessionId),
|
|
27
|
+
namespace: config.sessionNamespace,
|
|
28
|
+
labels: {
|
|
29
|
+
"app.kubernetes.io/managed-by": config.managedByLabel,
|
|
30
|
+
"telo.run/session-id": sessionId,
|
|
31
|
+
},
|
|
32
|
+
ownerReferences: [podOwnerRef(podName, podUid)],
|
|
33
|
+
},
|
|
34
|
+
spec: {
|
|
35
|
+
...(config.sessionRouting.ingressClassName
|
|
36
|
+
? { ingressClassName: config.sessionRouting.ingressClassName }
|
|
37
|
+
: {}),
|
|
38
|
+
// Present the predefined cert (e.g. a Cloudflare Origin cert) so an upstream
|
|
39
|
+
// in Full (Strict) mode can validate the origin. Only meaningful with routable
|
|
40
|
+
// hosts; a single wildcard `*.<domain>` Secret covers every session host.
|
|
41
|
+
...(config.sessionRouting.tlsSecretName && rules.length > 0
|
|
42
|
+
? {
|
|
43
|
+
tls: [
|
|
44
|
+
{
|
|
45
|
+
hosts: rules.map((r) => r.host),
|
|
46
|
+
secretName: config.sessionRouting.tlsSecretName,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
rules,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return { ingress, hosts: rules.map((r) => r.host) };
|
|
55
|
+
}
|
|
56
|
+
export function createIngressRouter(kube, config) {
|
|
57
|
+
const ns = config.sessionNamespace;
|
|
58
|
+
return {
|
|
59
|
+
layer: "ingress",
|
|
60
|
+
async publish(args) {
|
|
61
|
+
const { ingress } = buildSessionIngress(config, args.sessionId, args.serviceName, args.podName, args.podUid, args.ports);
|
|
62
|
+
const name = ingress.metadata.name;
|
|
63
|
+
const rules = ingress.spec?.rules ?? [];
|
|
64
|
+
// No tcp ports → nothing HTTP-routable. DELETE rather than return: a reload
|
|
65
|
+
// that drops the last routed port would otherwise leave the previous
|
|
66
|
+
// Ingress serving a port nothing listens on. (The Service stays for any udp.)
|
|
67
|
+
if (rules.length === 0) {
|
|
68
|
+
try {
|
|
69
|
+
await kube.networking.deleteNamespacedIngress({ name, namespace: ns });
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (!is404(err))
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
await createOrReplace(() => kube.networking.createNamespacedIngress({ namespace: ns, body: ingress }), async () => {
|
|
78
|
+
const existing = await kube.networking.readNamespacedIngress({ name, namespace: ns });
|
|
79
|
+
await kube.networking.replaceNamespacedIngress({
|
|
80
|
+
name,
|
|
81
|
+
namespace: ns,
|
|
82
|
+
body: {
|
|
83
|
+
...ingress,
|
|
84
|
+
metadata: {
|
|
85
|
+
...ingress.metadata,
|
|
86
|
+
resourceVersion: existing.metadata?.resourceVersion,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
return args.ports
|
|
92
|
+
.filter((p) => p.protocol === "tcp")
|
|
93
|
+
.map((p) => ({ host: hostForPort(config, args.sessionId, p.port), port: p.port }));
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Ingress offers no rejection signal — a controller that refuses a rule simply
|
|
97
|
+
* never writes status — so this only ever answers `programmed` or `pending`,
|
|
98
|
+
* and an unclaimed Ingress is caught by the deadline rather than by a reason
|
|
99
|
+
* the cluster gave. That is the weaker half of the two layers, and it is a
|
|
100
|
+
* property of the API, not of this implementation.
|
|
101
|
+
*/
|
|
102
|
+
async verdictFor(sessionId) {
|
|
103
|
+
try {
|
|
104
|
+
const ingress = await kube.networking.readNamespacedIngress({
|
|
105
|
+
name: sessionObjectName(sessionId),
|
|
106
|
+
namespace: ns,
|
|
107
|
+
});
|
|
108
|
+
const assigned = ingress.status?.loadBalancer?.ingress ?? [];
|
|
109
|
+
return assigned.length > 0 ? { kind: "programmed" } : { kind: "pending" };
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (is404(err))
|
|
113
|
+
return { kind: "pending" };
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
unclaimedReason() {
|
|
118
|
+
const cls = config.sessionRouting.ingressClassName;
|
|
119
|
+
return (`no ingress controller claimed the session Ingress` +
|
|
120
|
+
(cls ? ` for class '${cls}'` : " (no ingressClassName configured)") +
|
|
121
|
+
`. Check that an Ingress controller is installed and watching namespace '${ns}'.`);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=ingress-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingress-router.js","sourceRoot":"","sources":["../../../src/k8s/routing/ingress-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type RouteState } from "@telorun/runner-core";
|
|
2
|
+
import type { PublishedRoute, SessionRouter } from "./session-router.js";
|
|
3
|
+
export interface WatchRouteHealthOptions {
|
|
4
|
+
router: SessionRouter;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
/** The hosts `publish` reported. Empty → nothing to watch. */
|
|
7
|
+
routes: PublishedRoute[];
|
|
8
|
+
/** Receives every transition for one host. */
|
|
9
|
+
onState: (route: PublishedRoute, state: RouteState, reason?: string) => void;
|
|
10
|
+
/** Aborts the watch — wire to the session's teardown. */
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
/** How long a route may stay unclaimed before it is reported `unprogrammed`. */
|
|
13
|
+
timeoutMs: number;
|
|
14
|
+
/** Poll interval while waiting for a verdict. */
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
/** Receives a read failure once, so a permanent one (a missing RBAC grant, an
|
|
17
|
+
* unserved API version) reaches the runner's log and not only the client. */
|
|
18
|
+
onReadError?: (err: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Watch whether the routing layer actually programmed each published host.
|
|
22
|
+
*
|
|
23
|
+
* This is the check whose absence made an unroutable cluster indistinguishable
|
|
24
|
+
* from a healthy one: the runner already dials the workload's own address, which
|
|
25
|
+
* proves the app is listening and says nothing about whether traffic can reach
|
|
26
|
+
* it. A session whose routing objects are reconciled by no controller reported
|
|
27
|
+
* every port reachable while every public URL 404'd.
|
|
28
|
+
*
|
|
29
|
+
* REPORTS, never fails the session: a slow controller and an absent one look the
|
|
30
|
+
* same for the first few seconds, and killing a healthy session over routing that
|
|
31
|
+
* arrives late would trade a visible problem for a worse one.
|
|
32
|
+
*/
|
|
33
|
+
export declare function watchRouteHealth(options: WatchRouteHealthOptions): Promise<void>;
|
|
34
|
+
//# sourceMappingURL=route-health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-health.d.ts","sourceRoot":"","sources":["../../../src/k8s/routing/route-health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzE,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,8CAA8C;IAC9C,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7E,yDAAyD;IACzD,MAAM,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;kFAC8E;IAC9E,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CACtC;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDtF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { abortableDelay } from "@telorun/runner-core";
|
|
2
|
+
import { apiReason } from "../api-error.js";
|
|
3
|
+
const DEFAULT_INTERVAL_MS = 2_000;
|
|
4
|
+
/**
|
|
5
|
+
* Watch whether the routing layer actually programmed each published host.
|
|
6
|
+
*
|
|
7
|
+
* This is the check whose absence made an unroutable cluster indistinguishable
|
|
8
|
+
* from a healthy one: the runner already dials the workload's own address, which
|
|
9
|
+
* proves the app is listening and says nothing about whether traffic can reach
|
|
10
|
+
* it. A session whose routing objects are reconciled by no controller reported
|
|
11
|
+
* every port reachable while every public URL 404'd.
|
|
12
|
+
*
|
|
13
|
+
* REPORTS, never fails the session: a slow controller and an absent one look the
|
|
14
|
+
* same for the first few seconds, and killing a healthy session over routing that
|
|
15
|
+
* arrives late would trade a visible problem for a worse one.
|
|
16
|
+
*/
|
|
17
|
+
export async function watchRouteHealth(options) {
|
|
18
|
+
const { router, sessionId, routes, onState, signal } = options;
|
|
19
|
+
if (routes.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
const interval = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
22
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
23
|
+
const pending = new Map(routes.map((r) => [r.host, r]));
|
|
24
|
+
for (const route of routes)
|
|
25
|
+
onState(route, "pending");
|
|
26
|
+
// A read failure is NOT a verdict, but it is also not nothing: a permanent one
|
|
27
|
+
// (no RBAC on the routing objects, an API version this runner did not resolve)
|
|
28
|
+
// polls out the whole deadline and would otherwise be reported as
|
|
29
|
+
// `unclaimedReason()` — sending an operator to inspect the Gateway when the
|
|
30
|
+
// runner could not read its own object. So the last one is retained and wins
|
|
31
|
+
// at the deadline.
|
|
32
|
+
let lastReadError;
|
|
33
|
+
let reportedReadError = false;
|
|
34
|
+
while (pending.size > 0 && !signal.aborted) {
|
|
35
|
+
for (const [host, route] of [...pending]) {
|
|
36
|
+
if (signal.aborted)
|
|
37
|
+
return;
|
|
38
|
+
let verdict;
|
|
39
|
+
try {
|
|
40
|
+
verdict = await router.verdictFor(sessionId, route);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
lastReadError = err;
|
|
44
|
+
if (!reportedReadError) {
|
|
45
|
+
reportedReadError = true;
|
|
46
|
+
options.onReadError?.(err);
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// A verdict of ANY kind proves the read path works, so an earlier failure
|
|
51
|
+
// was transient and must not decide the message at the deadline.
|
|
52
|
+
lastReadError = undefined;
|
|
53
|
+
if (verdict.kind === "programmed") {
|
|
54
|
+
pending.delete(host);
|
|
55
|
+
onState(route, "programmed");
|
|
56
|
+
}
|
|
57
|
+
else if (verdict.kind === "rejected") {
|
|
58
|
+
pending.delete(host);
|
|
59
|
+
onState(route, "unprogrammed", verdict.reason);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (pending.size === 0)
|
|
63
|
+
return;
|
|
64
|
+
if (Date.now() >= deadline)
|
|
65
|
+
break;
|
|
66
|
+
await abortableDelay(interval, signal);
|
|
67
|
+
}
|
|
68
|
+
if (signal.aborted)
|
|
69
|
+
return;
|
|
70
|
+
const reason = lastReadError !== undefined
|
|
71
|
+
? `route status could not be read: ${apiReason(lastReadError)}`
|
|
72
|
+
: router.unclaimedReason();
|
|
73
|
+
for (const route of pending.values()) {
|
|
74
|
+
onState(route, "unprogrammed", reason);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=route-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-health.js","sourceRoot":"","sources":["../../../src/k8s/routing/route-health.ts"],"names":[],"mappings":"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"}
|