@mark1russell7/kind 0.10.0 → 0.11.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/build/judge.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as updatesOf, $t as Declaration, A as TriggerV1, At as rulesOf, B as RunCheck, Bt as ztunnelPodsOf, C as KindCellResult, Ct as outcomesOf, D as PredictionV1, Dt as requestsOf, E as LoadSample, Et as recordsOf, F as KIND_NOISE, Ft as slicesOf, G as judgeKind, Gt as PORT, H as checkRun, Ht as CLIENT, I as KindDecision, It as socketsOf, J as propagationOf, Jt as bandOf, K as observationAfter, Kt as REDIS, L as KindVerdict, Lt as stoppingOf, M as upgrade, Mt as signalsOf, N as upgradeCell, Nt as sliceDropOf, O as RunCheckV1, Ot as restartOf, P as Deleted, Pt as sliceEmptyOf, Q as unitOf, Qt as Band, R as LAST_SLACK, Rt as transitionOf, S as KindCellRecordV1, St as kernelCounts, T as KindVerdictV1, Tt as podsOf, U as deletionsOf, Ut as HEALTH_PORT, V as RunFacts, Vt as ztunnelReplaced, W as factsOf, Wt as LOG_LABEL, X as referenceOf, Xt as secondsOf, Y as propagationSource, Yt as seconds, Z as reusesReset, Zt as showBand, _ as ClusterManifest, _n as Workload, _t as dropOf, a as STAGES, an as MeshMode, at as RecordLine, b as FORMAT_V1, bt as intakeOf, c as readRecord, cn as RedisDeclaration, ct as SliceLine, d as calibrationOf, dn as ResetReuse, dt as TunnelLine, en as KindCell, et as Endpoint, f as cellsTable, fn as Stack, ft as ZtunnelPodLine, g as BandV1, gn as WindowPrediction, gt as deletionOf, h as timeSpread, hn as TriggerKind, ht as byNode, i as quantileOf, in as MeshFlavor, it as PropagationSource, j as judge, jt as signalOf, k as RunFactsV1, kt as restartsAt, l as Judged, ln as Reference, lt as SocketLine, m as predictionText, mn as Trigger, mt as byClients, n as medianInterval, nn as KubeletProbe, nt as Line, o as kindCellRecordSchema, on as PodSettings, ot as Request, p as medianText, pn as Template, pt as backendsAt, q as observationEnd, qt as SERVER, r as middle, rn as Mesh, rt as PodLine, s as kindCellRecordV1Schema, sn as Prediction, st as RulesLine, t as RELATIVE, tn as KindRun, tt as HBONE_PORT, u as TimeSpread, un as Replica, ut as TCP_STATE, v as EnvironmentManifest, vt as emptyOf, w as KindCellV1, wt as podsAt, x as KindCellRecord, xn as KubeProxyMode, xt as isTunnelState, y as FORMAT, yt as failuresOf, z as Propagated, zt as tunnelsOf } from "./judge-BD4UVQfy.js";
1
+ import { $ as updatesOf, $t as Declaration, A as TriggerV1, At as rulesOf, B as RunCheck, Bt as ztunnelPodsOf, C as KindCellResult, Ct as outcomesOf, D as PredictionV1, Dt as requestsOf, E as LoadSample, Et as recordsOf, F as KIND_NOISE, Ft as slicesOf, G as judgeKind, Gt as PORT, H as checkRun, Ht as CLIENT, I as KindDecision, It as socketsOf, J as propagationOf, Jt as bandOf, K as observationAfter, Kt as REDIS, L as KindVerdict, Lt as stoppingOf, M as upgrade, Mt as signalsOf, N as upgradeCell, Nt as sliceDropOf, O as RunCheckV1, Ot as restartOf, P as Deleted, Pt as sliceEmptyOf, Q as unitOf, Qt as Band, R as LAST_SLACK, Rt as transitionOf, S as KindCellRecordV1, St as kernelCounts, T as KindVerdictV1, Tt as podsOf, U as deletionsOf, Ut as HEALTH_PORT, V as RunFacts, Vt as ztunnelReplaced, W as factsOf, Wt as LOG_LABEL, X as referenceOf, Xt as secondsOf, Y as propagationSource, Yt as seconds, Z as reusesReset, Zt as showBand, _ as ClusterManifest, _n as Workload, _t as dropOf, a as STAGES, an as MeshMode, at as RecordLine, b as FORMAT_V1, bt as intakeOf, c as readRecord, cn as RedisDeclaration, ct as SliceLine, d as calibrationOf, dn as ResetReuse, dt as TunnelLine, en as KindCell, et as Endpoint, f as cellsTable, fn as Stack, ft as ZtunnelPodLine, g as BandV1, gn as WindowPrediction, gt as deletionOf, h as timeSpread, hn as TriggerKind, ht as byNode, i as quantileOf, in as MeshFlavor, it as PropagationSource, j as judge, jt as signalOf, k as RunFactsV1, kt as restartsAt, l as Judged, ln as Reference, lt as SocketLine, m as predictionText, mn as Trigger, mt as byClients, n as medianInterval, nn as KubeletProbe, nt as Line, o as kindCellRecordSchema, on as PodSettings, ot as Request, p as medianText, pn as Template, pt as backendsAt, q as observationEnd, qt as SERVER, r as middle, rn as Mesh, rt as PodLine, s as kindCellRecordV1Schema, sn as Prediction, st as RulesLine, t as RELATIVE, tn as KindRun, tt as HBONE_PORT, u as TimeSpread, un as Replica, ut as TCP_STATE, v as EnvironmentManifest, vt as emptyOf, w as KindCellV1, wt as podsAt, x as KindCellRecord, xn as KubeProxyMode, xt as isTunnelState, y as FORMAT, yt as failuresOf, z as Propagated, zt as tunnelsOf } from "./judge-4bzoeGIb.js";
2
2
  export { type Band, type BandV1, CLIENT, type ClusterManifest, type Declaration, type Deleted, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, HBONE_PORT, HEALTH_PORT, type Judged, KIND_NOISE, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindDecision, type KindRun, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, type Mesh, type MeshFlavor, type MeshMode, PORT, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, PropagationSource, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, SERVER, STAGES, SliceLine, SocketLine, type Stack, TCP_STATE, type Template, type TimeSpread, type Trigger, type TriggerKind, type TriggerV1, TunnelLine, type WindowPrediction, type Workload, ZtunnelPodLine, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, intakeOf, isTunnelState, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, medianInterval, medianText, middle, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartOf, restartsAt, reusesReset, rulesOf, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, socketsOf, stoppingOf, timeSpread, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, ztunnelPodsOf, ztunnelReplaced };
package/build/judge.js CHANGED
@@ -1 +1 @@
1
- export { ut as CLIENT, u as FORMAT, d as FORMAT_V1, N as HBONE_PORT, dt as HEALTH_PORT, h as KIND_NOISE, g as LAST_SLACK, ft as LOG_LABEL, pt as PORT, mt as REDIS, k as RELATIVE, ht as SERVER, t as STAGES, P as TCP_STATE, F as backendsAt, gt as bandOf, I as byClients, L as byNode, a as calibrationOf, o as cellsTable, _ as checkRun, R as deletionOf, v as deletionsOf, z as dropOf, B as emptyOf, y as factsOf, V as failuresOf, H as intakeOf, U as isTunnelState, f as judge, b as judgeKind, W as kernelCounts, n as kindCellRecordSchema, r as kindCellRecordV1Schema, A as medianInterval, s as medianText, j as middle, x as observationAfter, S as observationEnd, G as outcomesOf, K as podsAt, q as podsOf, c as predictionText, C as propagationOf, w as propagationSource, M as quantileOf, i as readRecord, J as recordsOf, T as referenceOf, Y as requestsOf, X as restartOf, Z as restartsAt, E as reusesReset, Q as rulesOf, _t as seconds, vt as secondsOf, yt as showBand, $ as signalOf, et as signalsOf, tt as sliceDropOf, nt as sliceEmptyOf, rt as slicesOf, it as socketsOf, at as stoppingOf, l as timeSpread, ot as transitionOf, st as tunnelsOf, D as unitOf, O as updatesOf, p as upgrade, m as upgradeCell, ct as ztunnelPodsOf, lt as ztunnelReplaced } from "./judge-BbLWVTYj.js";
1
+ export { ut as CLIENT, u as FORMAT, d as FORMAT_V1, N as HBONE_PORT, dt as HEALTH_PORT, h as KIND_NOISE, g as LAST_SLACK, ft as LOG_LABEL, pt as PORT, mt as REDIS, k as RELATIVE, ht as SERVER, t as STAGES, P as TCP_STATE, F as backendsAt, gt as bandOf, I as byClients, L as byNode, a as calibrationOf, o as cellsTable, _ as checkRun, R as deletionOf, v as deletionsOf, z as dropOf, B as emptyOf, y as factsOf, V as failuresOf, H as intakeOf, U as isTunnelState, f as judge, b as judgeKind, W as kernelCounts, n as kindCellRecordSchema, r as kindCellRecordV1Schema, A as medianInterval, s as medianText, j as middle, x as observationAfter, S as observationEnd, G as outcomesOf, K as podsAt, q as podsOf, c as predictionText, C as propagationOf, w as propagationSource, M as quantileOf, i as readRecord, J as recordsOf, T as referenceOf, Y as requestsOf, X as restartOf, Z as restartsAt, E as reusesReset, Q as rulesOf, _t as seconds, vt as secondsOf, yt as showBand, $ as signalOf, et as signalsOf, tt as sliceDropOf, nt as sliceEmptyOf, rt as slicesOf, it as socketsOf, at as stoppingOf, l as timeSpread, ot as transitionOf, st as tunnelsOf, D as unitOf, O as updatesOf, p as upgrade, m as upgradeCell, ct as ztunnelPodsOf, lt as ztunnelReplaced } from "./judge-DdeJ1qVd.js";
package/image/Dockerfile CHANGED
@@ -2,7 +2,7 @@
2
2
  # the agent of each node and the watcher of the API, on Node 24.21.0. The base is pinned by digest,
3
3
  # and npm-lock.json pins the packages from npm. The packages of the workspace come as the tarballs
4
4
  # that pnpm packs, and the build unpacks them over node_modules, as npm would install them. image.ts
5
- # stages the context and tags the image by its content.
5
+ # stages the context, and the resolver of the harness builds it with a tag from its content.
6
6
  FROM node:24.21.0-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1
7
7
  WORKDIR /opt/lab
8
8
  COPY package.json ./
package/image/app.mjs CHANGED
@@ -53,7 +53,8 @@ const redis =
53
53
  { clock, record },
54
54
  );
55
55
 
56
- // the flag file fails the check of the liveness restart: the agent writes it in the root of the container
56
+ // the flag file fails the check of the liveness restart: the agent writes it in the root of the container.
57
+ // With failsRoute of the config (cannot-serve), the flag also fails the route
57
58
  const FLAG = process.env.LAB_FLAG ?? "/tmp/lab-fail";
58
59
  const flag = () => (existsSync(FLAG) ? "error" : "ok");
59
60
 
@@ -68,7 +69,10 @@ const KNOBS = { server_idle_close: s(6), server_idle_sweep_interval: s(1), serve
68
69
  // service is not ready
69
70
  const server = http.createServer();
70
71
  server.on("listening", () => say({ ev: "app", what: "listening", port: config.port }));
71
- const transport = httpTransport(server, KNOBS, { listen: { port: config.port }, clock });
72
+ // lameDuck false of the declaration keeps each connection open while the service is not ready: then a
73
+ // caller learns only from the endpoints (the candidate A of S2). The default is the lame duck
74
+ const edge = declaration.lameDuck === false ? { keepAlive: () => true } : {};
75
+ const transport = httpTransport(server, KNOBS, { listen: { port: config.port }, clock, ...edge });
72
76
  // each request of the port is the one route. A failure of the route answers 503, as the lab always
73
77
  // did: the T07 and T18 cells count the 503s, and T18 predicts them while Redis is down. Each 503 gets
74
78
  // Retry-After from the rule of the transport
@@ -77,6 +81,7 @@ transport.mount(
77
81
  CONTROLLER,
78
82
  () => ROUTE,
79
83
  async (_req, signal) => {
84
+ if (config.failsRoute === true && existsSync(FLAG)) throw new Error("the replica cannot serve");
80
85
  if (redis !== undefined) {
81
86
  const sent = await redis.send("GET", ["lab"], { signal });
82
87
  if (sent.outcome !== "ok") throw new Error(`redis ${sent.outcome}: ${sent.error}`);
package/image/client.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  // A client of a kind cell: an open loop of requests to the ClusterIP of the service, each on a new
2
- // connection, thus kube-proxy picks a backend for each one. Each request is one JSON line with its
3
- // start on CLOCK_MONOTONIC, its duration, its outcome (ok, 503, refused, reset, timeout,
2
+ // connection, thus kube-proxy picks a backend for each one. With CONNECTIONS, the client keeps that
3
+ // count of keep-alive connections, and each request goes on the next one in turn. kube-proxy picks a
4
+ // backend only for a new connection. SIGHUP opens new connections. Each request is one JSON line with
5
+ // its start on CLOCK_MONOTONIC, its duration, its outcome (ok, 503, refused, reset, timeout,
4
6
  // unreachable, or another error) and its local port (`lp`).
5
7
  import http from "node:http";
6
8
 
@@ -10,6 +12,23 @@ const timeout = Number(process.env.TIMEOUT_MS ?? 1000);
10
12
  const pod = process.env.POD_NAME ?? "client";
11
13
  const node = process.env.NODE_NAME ?? "";
12
14
  const now = () => Number(process.hrtime.bigint()) / 1e6;
15
+ const connections = process.env.CONNECTIONS === undefined ? 0 : Number(process.env.CONNECTIONS);
16
+
17
+ // each agent holds one keep-alive connection. A `Connection: close` of the server ends it after its
18
+ // answer, and the next request on the agent opens a new connection
19
+ const agentsOf = () => Array.from({ length: connections }, () => new http.Agent({ keepAlive: true, maxSockets: 1 }));
20
+ let agents = agentsOf();
21
+ let turn = 0;
22
+ // SIGHUP: new agents for the next requests. The old agents go after the timeout, thus each request
23
+ // on them ends first
24
+ process.on("SIGHUP", () => {
25
+ const old = agents;
26
+ agents = agentsOf();
27
+ say({ ev: "client", what: "reconnect", t: now() });
28
+ setTimeout(() => {
29
+ for (const agent of old) agent.destroy();
30
+ }, timeout + 100);
31
+ });
13
32
 
14
33
  let pending = [];
15
34
  const flush = () => {
@@ -51,7 +70,11 @@ const fire = () => {
51
70
  lp ??= portOf(socket);
52
71
  say({ ev: "req", t, d: Math.round((now() - t) * 1000) / 1000, o, ...extra, lp });
53
72
  };
54
- const req = http.request({ host, port: Number(port), path: "/work", agent: false, headers: { connection: "close" } }, (res) => {
73
+ const options =
74
+ connections === 0
75
+ ? { agent: false, headers: { connection: "close" } }
76
+ : { agent: agents[turn++ % connections] };
77
+ const req = http.request({ host, port: Number(port), path: "/work", ...options }, (res) => {
55
78
  let body = "";
56
79
  res.setEncoding("utf8");
57
80
  res.on("data", (chunk) => {
@@ -73,12 +96,14 @@ const fire = () => {
73
96
  // connect() comes in the tick before this event, thus the handle has the port here, a refused
74
97
  // connection's too: in 200 refusals, each port was here and none at setImmediate
75
98
  lp = portOf(opened);
76
- opened.once("connect", () => {
77
- lp ??= portOf(opened);
78
- });
99
+ // a kept connection is already connected: a listener on it does not fire, and stays
100
+ if (opened.connecting)
101
+ opened.once("connect", () => {
102
+ lp ??= portOf(opened);
103
+ });
79
104
  });
80
105
  req.end();
81
106
  };
82
107
 
83
- say({ ev: "client", what: "start", t: now(), target: `${host}:${port}`, every, timeout });
108
+ say({ ev: "client", what: "start", t: now(), target: `${host}:${port}`, every, timeout, connections });
84
109
  setInterval(fire, every);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/kind",
4
- "version": "0.10.0",
4
+ "version": "0.11.0",
5
5
  "description": "Kubernetes in Docker as a lab for connection timers: a kind cluster from a scenario, faults in the namespaces of its pods, observations on the one monotonic clock of the VM, and predictions from the ordering law. Its judge entry reads and judges a raw record without Docker.",
6
6
  "keywords": [
7
7
  "kubernetes",
@@ -67,7 +67,7 @@
67
67
  "dependencies": {
68
68
  "@mark1russell7/harness": "^0.12.4",
69
69
  "@mark1russell7/quantity": "^0.2.0",
70
- "@mark1russell7/service": "^0.5.16",
70
+ "@mark1russell7/service": "^0.5.18",
71
71
  "testcontainers": "12.2.0"
72
72
  },
73
73
  "peerDependencies": {