@mark1russell7/kind 0.3.0 → 0.4.1

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.
@@ -117,9 +117,10 @@ type Stack = "service" | "grpc";
117
117
  * - `app-stall` blocks the event loop of one replica.
118
118
  * - `rollout` is a rolling update while Redis is down.
119
119
  * - `cpu-throttle` sets `cpu.max` in the cgroup of one replica.
120
+ * - `authorization-policy` applies an AuthorizationPolicy of Istio that denies the clients, for the hold (T15).
120
121
  * - `none` injects nothing: a calibration measures the path itself.
121
122
  */
122
- type TriggerKind = "pod-deletion" | "rolling-update" | "node-drain" | "scale-in" | "crash" | "liveness-restart" | "freeze-process" | "rst" | "blackhole" | "peer-gone-without-close" | "app-stall" | "rollout" | "cpu-throttle";
123
+ type TriggerKind = "pod-deletion" | "rolling-update" | "node-drain" | "scale-in" | "crash" | "liveness-restart" | "freeze-process" | "rst" | "blackhole" | "peer-gone-without-close" | "app-stall" | "rollout" | "cpu-throttle" | "authorization-policy";
123
124
  /**
124
125
  * A trigger is its kind and the settings of its fault. The parameter `A` is the form of an amount:
125
126
  * a quantity in the format `/2`, a number in the format `/1`.
@@ -135,6 +136,12 @@ interface Trigger<A = Quantity> {
135
136
  readonly quota: A;
136
137
  readonly period: A;
137
138
  } | undefined;
139
+ /**
140
+ * This name is the service account that an AuthorizationPolicy denies (`authorization-policy`). The
141
+ * default is `default`, the account of the clients. An account that no pod has changes the policy,
142
+ * and denies no call.
143
+ */
144
+ readonly account?: string | undefined;
138
145
  }
139
146
  /** A kubelet probe is the period, the failure threshold and the timeout of one probe of the pod. */
140
147
  interface KubeletProbe {
@@ -211,8 +218,9 @@ interface Band<A = Quantity> {
211
218
  * - `intake`: the stop of the intake of the target pod, as its service records it.
212
219
  * - `fault`: the commit of the fault, as the injector or the agent stamps it.
213
220
  * - `resume`: the end of a pause of kube-proxy, as the agent stamps it.
221
+ * - `heal`: the start of the first act that ends the fault, for example the delete of a policy.
214
222
  */
215
- type Reference = "deletion" | "intake" | "fault" | "resume";
223
+ type Reference = "deletion" | "intake" | "fault" | "resume" | "heal";
216
224
  /**
217
225
  * A reset reuse is how a reset fails a later connection on the same port. A reset from the server
218
226
  * leaves the conntrack entry of its flow in CLOSE, with the NAT to that server, for `life`. A SYN on
@@ -273,6 +281,8 @@ type Prediction<A = Quantity> = {
273
281
  * earlier drop only closes the window earlier.
274
282
  */
275
283
  readonly propagation?: Band<A> | undefined;
284
+ /** These outcomes are the only failures that the window permits. A failure with another outcome fails the run. Without them, each outcome but `ok` can fail. */
285
+ readonly outcomes?: readonly string[] | undefined;
276
286
  readonly why: string;
277
287
  } | {
278
288
  readonly kind: "removal";
@@ -596,6 +606,10 @@ interface RunFacts<A = Quantity> {
596
606
  * run with a deletion after its start, with the stamps of its intake and of its deletion.
597
607
  */
598
608
  readonly deletions?: readonly Deleted[] | undefined;
609
+ /** These are the first LDS updates of the sidecar of each server pod after the fault, by pod (`updatesOf`). */
610
+ readonly updates?: Readonly<Record<string, Propagated<A> | null>> | undefined;
611
+ /** These are the first LDS updates of the sidecar of each server pod after the heal, by pod. */
612
+ readonly healUpdates?: Readonly<Record<string, Propagated<A> | null>> | undefined;
599
613
  }
600
614
  /** A deleted replica is a replica of a run that got a deletion. It gives its pod, and the stamps of its intake (or null) and of its deletion. */
601
615
  interface Deleted {
@@ -666,6 +680,14 @@ declare const propagationSource: (cell: Pick<KindCell, "mesh">) => PropagationSo
666
680
  * The source `eds` gives the poll of the client sidecar of each node.
667
681
  */
668
682
  declare const propagationOf: (run: KindRun, from: number | null, ip?: string | undefined, source?: PropagationSource) => Record<string, Propagated | null>;
683
+ /**
684
+ * This function gives the first LDS update of the sidecar of each server pod after the stamp `from`.
685
+ * The update came between the poll before (`after`) and the poll that saw it (`by`), after `from`.
686
+ * A poller gives a line at each change of its counters. Thus a line after `from` with a poll before
687
+ * it is an update, when its count `mod` is above the count before `from`. The first line of a poller
688
+ * has no poll before it (`prev` is null), and it is no update.
689
+ */
690
+ declare const updatesOf: (run: Pick<KindRun, "lines">, from: number | null) => Record<string, Propagated | null>;
669
691
  /**
670
692
  * This function gives each replica of a run that got a deletion after the start of the run, in the
671
693
  * sequence of the deletions. A rolling update deletes each old replica in turn.
@@ -809,12 +831,17 @@ declare const cellsTable: (judged: readonly Judged[]) => string;
809
831
  * This function gives the propagation of each calibration, by node. It is the time from the
810
832
  * deletion to the poll of the rules of each node without the pod. It also gives the time to the
811
833
  * EndpointSlice and to the stop of the intake.
834
+ *
835
+ * For a policy of Istio, the propagation is the LDS update of each server sidecar after the apply,
836
+ * by node (`updatesOf`). Then `heal` is the update after the delete of the policy. `slice` and
837
+ * `intake` are null.
812
838
  */
813
839
  declare const calibrationOf: (judged: Judged) => {
814
840
  readonly nodes: Readonly<Record<string, TimeSpread | null>>;
815
841
  readonly all: TimeSpread | null;
816
842
  readonly slice: TimeSpread | null;
817
843
  readonly intake: TimeSpread | null;
844
+ readonly heal?: TimeSpread | null | undefined;
818
845
  };
819
846
  //#endregion
820
847
  //#region src/schema.d.ts
@@ -849,5 +876,5 @@ declare const quantileOf: (sorted: readonly number[], p: number) => number;
849
876
  */
850
877
  declare const medianInterval: (sorted: readonly number[]) => [number, number];
851
878
  //#endregion
852
- export { Endpoint as $, ResetReuse as $t, TriggerV1 as A, CLIENT as At, RunCheck as B, Band as Bt, KindCellResult as C, signalOf as Ct, PredictionV1 as D, slicesOf as Dt, LoadSample as E, sliceEmptyOf as Et, KIND_NOISE as F, SERVER as Ft, judgeKind as G, Mesh as Gt, checkRun as H, KindCell as Ht, KindDecision as I, bandOf as It, propagationOf as J, PodSettings as Jt, observationAfter as K, MeshFlavor as Kt, KindVerdict as L, seconds as Lt, upgrade as M, LOG_LABEL as Mt, upgradeCell as N, PORT as Nt, RunCheckV1 as O, stoppingOf as Ot, Deleted as P, REDIS as Pt, unitOf as Q, Replica as Qt, LAST_SLACK as R, secondsOf as Rt, KindCellRecordV1 as S, rulesOf as St, KindVerdictV1 as T, sliceDropOf as Tt, deletionsOf as U, KindRun as Ut, RunFacts as V, Declaration as Vt, factsOf as W, KubeletProbe as Wt, referenceOf as X, RedisDeclaration as Xt, propagationSource as Y, Prediction as Yt, reusesReset as Z, Reference as Zt, ClusterManifest as _, scenario as _n, podsAt as _t, STAGES as a, KUBE_PROXY_MODES as an, RulesLine as at, FORMAT_V1 as b, requestsOf as bt, readRecord as c, KubeProxyMode as cn, byClients as ct, calibrationOf as d, cidrOverlap as dn, dropOf as dt, Stack as en, Line as et, cellsTable as f, cidrRange as fn, emptyOf as ft, BandV1 as g, nodeNames as gn, outcomesOf as gt, timeSpread as h, kubeProxyPatch as hn, kernelCounts as ht, quantileOf as i, Workload as in, Request as it, judge as j, HEALTH_PORT as jt, RunFactsV1 as k, transitionOf as kt, Judged as l, NODE_IMAGES as ln, byNode as lt, predictionText as m, kindConfig as mn, intakeOf as mt, medianInterval as n, Trigger as nn, PropagationSource as nt, kindCellRecordSchema as o, KindConfig as on, SliceLine as ot, medianText as p, goDuration as pn, failuresOf as pt, observationEnd as q, MeshMode as qt, middle as r, TriggerKind as rn, RecordLine as rt, kindCellRecordV1Schema as s, KindScenario as sn, backendsAt as st, RELATIVE as t, Template as tn, PodLine as tt, TimeSpread as u, NodeImage as un, deletionOf as ut, EnvironmentManifest as v, scenarioProblems as vn, podsOf as vt, KindCellV1 as w, signalsOf as wt, KindCellRecord as x, restartsAt as xt, FORMAT as y, recordsOf as yt, Propagated as z, showBand as zt };
853
- //# sourceMappingURL=judge-DWF4QdZ8.d.ts.map
879
+ export { updatesOf as $, Replica as $t, TriggerV1 as A, transitionOf as At, RunCheck as B, showBand as Bt, KindCellResult as C, rulesOf as Ct, PredictionV1 as D, sliceEmptyOf as Dt, LoadSample as E, sliceDropOf as Et, KIND_NOISE as F, REDIS as Ft, judgeKind as G, KubeletProbe as Gt, checkRun as H, Declaration as Ht, KindDecision as I, SERVER as It, propagationOf as J, MeshMode as Jt, observationAfter as K, Mesh as Kt, KindVerdict as L, bandOf as Lt, upgrade as M, HEALTH_PORT as Mt, upgradeCell as N, LOG_LABEL as Nt, RunCheckV1 as O, slicesOf as Ot, Deleted as P, PORT as Pt, unitOf as Q, Reference as Qt, LAST_SLACK as R, seconds as Rt, KindCellRecordV1 as S, restartsAt as St, KindVerdictV1 as T, signalsOf as Tt, deletionsOf as U, KindCell as Ut, RunFacts as V, Band as Vt, factsOf as W, KindRun as Wt, referenceOf as X, Prediction as Xt, propagationSource as Y, PodSettings as Yt, reusesReset as Z, RedisDeclaration as Zt, ClusterManifest as _, nodeNames as _n, outcomesOf as _t, STAGES as a, Workload as an, Request as at, FORMAT_V1 as b, recordsOf as bt, readRecord as c, KindScenario as cn, backendsAt as ct, calibrationOf as d, NodeImage as dn, deletionOf as dt, ResetReuse as en, Endpoint as et, cellsTable as f, cidrOverlap as fn, dropOf as ft, BandV1 as g, kubeProxyPatch as gn, kernelCounts as gt, timeSpread as h, kindConfig as hn, intakeOf as ht, quantileOf as i, TriggerKind as in, RecordLine as it, judge as j, CLIENT as jt, RunFactsV1 as k, stoppingOf as kt, Judged as l, KubeProxyMode as ln, byClients as lt, predictionText as m, goDuration as mn, failuresOf as mt, medianInterval as n, Template as nn, PodLine as nt, kindCellRecordSchema as o, KUBE_PROXY_MODES as on, RulesLine as ot, medianText as p, cidrRange as pn, emptyOf as pt, observationEnd as q, MeshFlavor as qt, middle as r, Trigger as rn, PropagationSource as rt, kindCellRecordV1Schema as s, KindConfig as sn, SliceLine as st, RELATIVE as t, Stack as tn, Line as tt, TimeSpread as u, NODE_IMAGES as un, byNode as ut, EnvironmentManifest as v, scenario as vn, podsAt as vt, KindCellV1 as w, signalOf as wt, KindCellRecord as x, requestsOf as xt, FORMAT as y, scenarioProblems as yn, podsOf as yt, Propagated as z, secondsOf as zt };
880
+ //# sourceMappingURL=judge-DMuI3aDn.d.ts.map
package/build/judge.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as Endpoint, $t as ResetReuse, A as TriggerV1, At as CLIENT, B as RunCheck, Bt as Band, C as KindCellResult, Ct as signalOf, D as PredictionV1, Dt as slicesOf, E as LoadSample, Et as sliceEmptyOf, F as KIND_NOISE, Ft as SERVER, G as judgeKind, Gt as Mesh, H as checkRun, Ht as KindCell, I as KindDecision, It as bandOf, J as propagationOf, Jt as PodSettings, K as observationAfter, Kt as MeshFlavor, L as KindVerdict, Lt as seconds, M as upgrade, Mt as LOG_LABEL, N as upgradeCell, Nt as PORT, O as RunCheckV1, Ot as stoppingOf, P as Deleted, Pt as REDIS, Q as unitOf, Qt as Replica, R as LAST_SLACK, Rt as secondsOf, S as KindCellRecordV1, St as rulesOf, T as KindVerdictV1, Tt as sliceDropOf, U as deletionsOf, Ut as KindRun, V as RunFacts, Vt as Declaration, W as factsOf, Wt as KubeletProbe, X as referenceOf, Xt as RedisDeclaration, Y as propagationSource, Yt as Prediction, Z as reusesReset, Zt as Reference, _ as ClusterManifest, _t as podsAt, a as STAGES, at as RulesLine, b as FORMAT_V1, bt as requestsOf, c as readRecord, cn as KubeProxyMode, ct as byClients, d as calibrationOf, dt as dropOf, en as Stack, et as Line, f as cellsTable, ft as emptyOf, g as BandV1, gt as outcomesOf, h as timeSpread, ht as kernelCounts, i as quantileOf, in as Workload, it as Request, j as judge, jt as HEALTH_PORT, k as RunFactsV1, kt as transitionOf, l as Judged, lt as byNode, m as predictionText, mt as intakeOf, n as medianInterval, nn as Trigger, nt as PropagationSource, o as kindCellRecordSchema, ot as SliceLine, p as medianText, pt as failuresOf, q as observationEnd, qt as MeshMode, r as middle, rn as TriggerKind, rt as RecordLine, s as kindCellRecordV1Schema, st as backendsAt, t as RELATIVE, tn as Template, tt as PodLine, u as TimeSpread, ut as deletionOf, v as EnvironmentManifest, vt as podsOf, w as KindCellV1, wt as signalsOf, x as KindCellRecord, xt as restartsAt, y as FORMAT, yt as recordsOf, z as Propagated, zt as showBand } from "./judge-DWF4QdZ8.js";
2
- export { type Band, type BandV1, CLIENT, type ClusterManifest, type Declaration, type Deleted, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, 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, type Stack, type Template, type TimeSpread, type Trigger, type TriggerKind, type TriggerV1, type Workload, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, medianInterval, medianText, middle, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stoppingOf, timeSpread, transitionOf, unitOf, upgrade, upgradeCell };
1
+ import { $ as updatesOf, $t as Replica, A as TriggerV1, At as transitionOf, B as RunCheck, Bt as showBand, C as KindCellResult, Ct as rulesOf, D as PredictionV1, Dt as sliceEmptyOf, E as LoadSample, Et as sliceDropOf, F as KIND_NOISE, Ft as REDIS, G as judgeKind, Gt as KubeletProbe, H as checkRun, Ht as Declaration, I as KindDecision, It as SERVER, J as propagationOf, Jt as MeshMode, K as observationAfter, Kt as Mesh, L as KindVerdict, Lt as bandOf, M as upgrade, Mt as HEALTH_PORT, N as upgradeCell, Nt as LOG_LABEL, O as RunCheckV1, Ot as slicesOf, P as Deleted, Pt as PORT, Q as unitOf, Qt as Reference, R as LAST_SLACK, Rt as seconds, S as KindCellRecordV1, St as restartsAt, T as KindVerdictV1, Tt as signalsOf, U as deletionsOf, Ut as KindCell, V as RunFacts, Vt as Band, W as factsOf, Wt as KindRun, X as referenceOf, Xt as Prediction, Y as propagationSource, Yt as PodSettings, Z as reusesReset, Zt as RedisDeclaration, _ as ClusterManifest, _t as outcomesOf, a as STAGES, an as Workload, at as Request, b as FORMAT_V1, bt as recordsOf, c as readRecord, ct as backendsAt, d as calibrationOf, dt as deletionOf, en as ResetReuse, et as Endpoint, f as cellsTable, ft as dropOf, g as BandV1, gt as kernelCounts, h as timeSpread, ht as intakeOf, i as quantileOf, in as TriggerKind, it as RecordLine, j as judge, jt as CLIENT, k as RunFactsV1, kt as stoppingOf, l as Judged, ln as KubeProxyMode, lt as byClients, m as predictionText, mt as failuresOf, n as medianInterval, nn as Template, nt as PodLine, o as kindCellRecordSchema, ot as RulesLine, p as medianText, pt as emptyOf, q as observationEnd, qt as MeshFlavor, r as middle, rn as Trigger, rt as PropagationSource, s as kindCellRecordV1Schema, st as SliceLine, t as RELATIVE, tn as Stack, tt as Line, u as TimeSpread, ut as byNode, v as EnvironmentManifest, vt as podsAt, w as KindCellV1, wt as signalOf, x as KindCellRecord, xt as requestsOf, y as FORMAT, yt as podsOf, z as Propagated, zt as secondsOf } from "./judge-DMuI3aDn.js";
2
+ export { type Band, type BandV1, CLIENT, type ClusterManifest, type Declaration, type Deleted, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, 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, type Stack, type Template, type TimeSpread, type Trigger, type TriggerKind, type TriggerV1, type Workload, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, medianInterval, medianText, middle, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stoppingOf, timeSpread, transitionOf, unitOf, updatesOf, upgrade, upgradeCell };
package/build/judge.js CHANGED
@@ -1 +1 @@
1
- export { tt as CLIENT, u as FORMAT, d as FORMAT_V1, nt as HEALTH_PORT, h as KIND_NOISE, g as LAST_SLACK, rt as LOG_LABEL, it as PORT, at as REDIS, O as RELATIVE, ot as SERVER, t as STAGES, M as backendsAt, st as bandOf, N as byClients, P as byNode, a as calibrationOf, o as cellsTable, _ as checkRun, F as deletionOf, v as deletionsOf, I as dropOf, L as emptyOf, y as factsOf, R as failuresOf, z as intakeOf, f as judge, b as judgeKind, B as kernelCounts, n as kindCellRecordSchema, r as kindCellRecordV1Schema, k as medianInterval, s as medianText, A as middle, x as observationAfter, S as observationEnd, V as outcomesOf, H as podsAt, U as podsOf, c as predictionText, C as propagationOf, w as propagationSource, j as quantileOf, i as readRecord, W as recordsOf, T as referenceOf, G as requestsOf, K as restartsAt, E as reusesReset, q as rulesOf, ct as seconds, lt as secondsOf, ut as showBand, J as signalOf, Y as signalsOf, X as sliceDropOf, Z as sliceEmptyOf, Q as slicesOf, $ as stoppingOf, l as timeSpread, et as transitionOf, D as unitOf, p as upgrade, m as upgradeCell } from "./judge-CRMYoEOw.js";
1
+ export { nt as CLIENT, u as FORMAT, d as FORMAT_V1, rt as HEALTH_PORT, h as KIND_NOISE, g as LAST_SLACK, it as LOG_LABEL, at as PORT, ot as REDIS, k as RELATIVE, st as SERVER, t as STAGES, N as backendsAt, ct as bandOf, P as byClients, F as byNode, a as calibrationOf, o as cellsTable, _ as checkRun, I as deletionOf, v as deletionsOf, L as dropOf, R as emptyOf, y as factsOf, z as failuresOf, B as intakeOf, f as judge, b as judgeKind, V as kernelCounts, n as kindCellRecordSchema, r as kindCellRecordV1Schema, A as medianInterval, s as medianText, j as middle, x as observationAfter, S as observationEnd, H as outcomesOf, U as podsAt, W as podsOf, c as predictionText, C as propagationOf, w as propagationSource, M as quantileOf, i as readRecord, G as recordsOf, T as referenceOf, K as requestsOf, q as restartsAt, E as reusesReset, J as rulesOf, lt as seconds, ut as secondsOf, dt as showBand, Y as signalOf, X as signalsOf, Z as sliceDropOf, Q as sliceEmptyOf, $ as slicesOf, et as stoppingOf, l as timeSpread, tt as transitionOf, D as unitOf, O as updatesOf, p as upgrade, m as upgradeCell } from "./judge-Bkbs7VVG.js";
package/image/Dockerfile CHANGED
@@ -14,5 +14,5 @@ RUN for name in quantity algebra service; do \
14
14
  && tar -xzf /tmp/workspace/mark1russell7-$name-*.tgz -C "node_modules/@mark1russell7/$name" --strip-components=1; \
15
15
  done \
16
16
  && rm -rf /tmp/workspace
17
- COPY app.mjs client.mjs client-grpc.mjs agent.mjs watch.mjs compose.ts backends.ts conntrack.ts drops.ts eds.ts ./
17
+ COPY app.mjs client.mjs client-grpc.mjs agent.mjs watch.mjs compose.ts backends.ts conntrack.ts drops.ts eds.ts lds.ts ./
18
18
  CMD ["node", "app.mjs"]
package/image/agent.mjs CHANGED
@@ -11,10 +11,14 @@
11
11
  // second it reads the counters of dropped packets of the node and
12
12
  // of each client pod, and prints each one that grew (`drops`).
13
13
  // As /tmp/eds.json asks, it starts the EDS poller of each client pod
14
- // of the node, and stops it when the file goes
14
+ // of the node, and stops it when the file goes. /tmp/lds.json does the
15
+ // same for the LDS poller of each server pod
15
16
  // node agent.mjs eds JSON the EDS poller: in the network namespace of a client pod (nsenter -n),
16
17
  // it reads /clusters of the Envoy of the sidecar each `every` ms, and
17
18
  // prints the hosts of the server cluster at each change (`eds`)
19
+ // node agent.mjs lds JSON the LDS poller: in the network namespace of a server pod, it reads
20
+ // the counters of the listener manager of the sidecar each `every` ms,
21
+ // and prints them at each change (`lds`)
18
22
  // node agent.mjs act JSON does one act and prints its stamps on CLOCK_MONOTONIC
19
23
  //
20
24
  // The acts: signal (a signal to a PID), pause (SIGSTOP, then SIGCONT after ms), write (a file, for
@@ -26,6 +30,7 @@ import { backendsOf } from "./backends.ts";
26
30
  import { entriesOf, flowsOf, staleOf, stalledOf } from "./conntrack.ts";
27
31
  import { devOf, grownOf, snmpOf, softnetOf, unresolvedOf } from "./drops.ts";
28
32
  import { clusterFilter, hostsOf } from "./eds.ts";
33
+ import { LDS_FILTER, listenerCountsOf } from "./lds.ts";
29
34
 
30
35
  const now = () => Number(process.hrtime.bigint()) / 1e6;
31
36
  const node = process.env.NODE_NAME ?? "";
@@ -261,12 +266,15 @@ const serve = async () => {
261
266
  };
262
267
 
263
268
  /**
264
- * the EDS pollers of the node: /tmp/eds.json gives the server cluster, the interval and each client
265
- * pod of the node with a PID in it. The agent starts one poller for each pod in the network
266
- * namespace of that PID, and stops each one when the file changes or goes. nsenter -n keeps the
267
- * mount namespace of the agent, thus the poller is this file, and its clock is the one of the VM
269
+ * the pollers of the sidecars of the node, of one kind: `eds` or `lds`. /tmp/<kind>.json gives the
270
+ * interval and each pod of the node with a PID in it, and for `eds` the server cluster. The agent
271
+ * starts one poller for each pod in the network namespace of that PID, and stops each one when the
272
+ * file changes or goes. nsenter -n keeps the mount namespace of the agent, thus the poller is this
273
+ * file, and its clock is the one of the VM. The EDS pollers read the client pods, and the LDS
274
+ * pollers read the server pods
268
275
  */
269
- const pollers = async () => {
276
+ const pollers = async (kind) => {
277
+ const file = `/tmp/${kind}.json`;
270
278
  let config = null;
271
279
  let children = [];
272
280
  const stop = () => {
@@ -277,10 +285,10 @@ const pollers = async () => {
277
285
  let text = null;
278
286
  let spec = null;
279
287
  try {
280
- text = existsSync("/tmp/eds.json") ? readFileSync("/tmp/eds.json", "utf8") : null;
288
+ text = existsSync(file) ? readFileSync(file, "utf8") : null;
281
289
  spec = text === null ? null : JSON.parse(text);
282
290
  } catch (error) {
283
- say({ ev: "eds-fail", t: now(), why: String(error?.message ?? error).slice(0, 300) });
291
+ say({ ev: `${kind}-fail`, t: now(), why: String(error?.message ?? error).slice(0, 300) });
284
292
  text = config;
285
293
  }
286
294
  if (text !== config) {
@@ -288,15 +296,15 @@ const pollers = async () => {
288
296
  config = text;
289
297
  if (spec !== null) {
290
298
  for (const { pod, pid } of spec.pods) {
291
- const argv = ["-t", String(pid), "-n", "--", process.execPath, process.argv[1], "eds", JSON.stringify({ cluster: spec.cluster, every: spec.every, pod })];
299
+ const argv = ["-t", String(pid), "-n", "--", process.execPath, process.argv[1], kind, JSON.stringify({ cluster: spec.cluster, every: spec.every, pod })];
292
300
  const child = spawn("nsenter", argv, { stdio: ["ignore", "inherit", "inherit"] });
293
301
  child.on("exit", (code, signal) => {
294
- if (signal !== "SIGKILL") say({ ev: "eds-exit", t: now(), pod, code, signal });
302
+ if (signal !== "SIGKILL") say({ ev: `${kind}-exit`, t: now(), pod, code, signal });
295
303
  });
296
- child.on("error", (error) => say({ ev: "eds-fail", t: now(), pod, why: String(error?.message ?? error).slice(0, 300) }));
304
+ child.on("error", (error) => say({ ev: `${kind}-fail`, t: now(), pod, why: String(error?.message ?? error).slice(0, 300) }));
297
305
  children.push(child);
298
306
  }
299
- say({ ev: "eds-start", t: now(), cluster: spec.cluster, every: spec.every, pods: spec.pods.map((each) => each.pod) });
307
+ say({ ev: `${kind}-start`, t: now(), ...(spec.cluster === undefined ? {} : { cluster: spec.cluster }), every: spec.every, pods: spec.pods.map((each) => each.pod) });
300
308
  }
301
309
  }
302
310
  await sleep(250);
@@ -304,13 +312,12 @@ const pollers = async () => {
304
312
  };
305
313
 
306
314
  /**
307
- * one EDS poller. The admin endpoint of the sidecar is 127.0.0.1:15000 in the namespace of the pod
308
- * (envoy_bootstrap.json of Istio 1.31.3). The filter of /clusters keeps the server cluster only
309
- * (clusters_handler.cc of Envoy v1.39.4). A line comes at each change of its hosts, with the start of
310
- * the poll before it (`prev`), as a rules line. Each second, a line `eds-alive` counts the polls
315
+ * one poll loop of the admin endpoint of a sidecar, 127.0.0.1:15000 in the namespace of the pod
316
+ * (envoy_bootstrap.json of Istio 1.31.3), each `every` ms. `read` gives the state of one answer, and
317
+ * a line comes at each change of that state, with the start of the poll before it (`prev`), as a rules
318
+ * line. Each second, a line `<kind>-alive` counts the polls
311
319
  */
312
- const eds = async ({ cluster, every, pod }) => {
313
- const url = `http://127.0.0.1:15000/clusters?filter=${encodeURIComponent(clusterFilter(cluster))}`;
320
+ const poll = async (kind, url, every, pod, read) => {
314
321
  let last = null;
315
322
  let previous = null;
316
323
  let failure = null;
@@ -326,22 +333,22 @@ const eds = async ({ cluster, every, pod }) => {
326
333
  polls += 1;
327
334
  spent += took;
328
335
  if (!response.ok) throw new Error(`the admin endpoint answered ${response.status}: ${text.slice(0, 200)}`);
329
- const { b, u } = hostsOf(text, cluster);
330
- const key = JSON.stringify([b, u]);
336
+ const state = read(text);
337
+ const key = JSON.stringify(state);
331
338
  if (key !== last) {
332
- say({ ev: "eds", t, prev: previous, pod, took: Math.round(took * 1000) / 1000, b, ...(Object.keys(u).length === 0 ? {} : { u }) });
339
+ say({ ev: kind, t, prev: previous, pod, took: Math.round(took * 1000) / 1000, ...state });
333
340
  last = key;
334
341
  }
335
342
  failure = null;
336
343
  } catch (error) {
337
344
  // each failure once, until a poll succeeds
338
345
  const why = String(error?.message ?? error).slice(0, 300);
339
- if (why !== failure) say({ ev: "eds-fail", t, pod, why });
346
+ if (why !== failure) say({ ev: `${kind}-fail`, t, pod, why });
340
347
  failure = why;
341
348
  }
342
349
  previous = t;
343
350
  if (t - beat >= 1000) {
344
- say({ ev: "eds-alive", t, pod, polls, ms: polls === 0 ? 0 : Math.round((spent / polls) * 100) / 100 });
351
+ say({ ev: `${kind}-alive`, t, pod, polls, ms: polls === 0 ? 0 : Math.round((spent / polls) * 100) / 100 });
345
352
  beat = t;
346
353
  polls = 0;
347
354
  spent = 0;
@@ -351,6 +358,27 @@ const eds = async ({ cluster, every, pod }) => {
351
358
  }
352
359
  };
353
360
 
361
+ /**
362
+ * one EDS poller. The filter of /clusters keeps the server cluster only (clusters_handler.cc of Envoy
363
+ * v1.39.4). A line `eds` gives the healthy hosts (`b`), and each other host with its flags (`u`)
364
+ */
365
+ const eds = ({ cluster, every, pod }) =>
366
+ poll("eds", `http://127.0.0.1:15000/clusters?filter=${encodeURIComponent(clusterFilter(cluster))}`, every, pod, (text) => {
367
+ const { b, u } = hostsOf(text, cluster);
368
+ return { b, ...(Object.keys(u).length === 0 ? {} : { u }) };
369
+ });
370
+
371
+ /**
372
+ * one LDS poller, in a server pod. The filter of /stats keeps the two counters of the listener
373
+ * manager. A line `lds` gives each update of a listener (`mod`) and each update in place (`inPlace`)
374
+ */
375
+ const lds = ({ every, pod }) =>
376
+ poll("lds", `http://127.0.0.1:15000/stats?filter=${encodeURIComponent(LDS_FILTER)}`, every, pod, (text) => {
377
+ const counts = listenerCountsOf(text);
378
+ if (counts === null) throw new Error(`the stats have no listener_modified: ${text.slice(0, 200)}`);
379
+ return counts;
380
+ });
381
+
354
382
  const act = async (spec) => {
355
383
  const t0 = now();
356
384
  if (spec.op === "now") return { t0, t1: t0 };
@@ -389,10 +417,13 @@ const act = async (spec) => {
389
417
  const [mode, arg] = process.argv.slice(2);
390
418
  if (mode === "serve") {
391
419
  say({ ev: "agent", what: "start", t: now() });
392
- void pollers();
420
+ void pollers("eds");
421
+ void pollers("lds");
393
422
  await serve();
394
423
  } else if (mode === "eds") {
395
424
  await eds(JSON.parse(arg));
425
+ } else if (mode === "lds") {
426
+ await lds(JSON.parse(arg));
396
427
  } else if (mode === "act") {
397
428
  const spec = JSON.parse(arg);
398
429
  try {
@@ -64,6 +64,8 @@ const outcomeOf = (error) => {
64
64
  if (/ECONNRESET|EPIPE/.test(text)) return "reset";
65
65
  if (/EHOSTUNREACH|ENETUNREACH/.test(text)) return "unreachable";
66
66
  if (error.code === grpc.status.DEADLINE_EXCEEDED) return "timeout";
67
+ // the local reply 403 of an RBAC deny of a sidecar of Istio gives PERMISSION_DENIED (status.cc of Envoy v1.39.4, lines 14–15)
68
+ if (error.code === grpc.status.PERMISSION_DENIED) return "denied";
67
69
  return "error";
68
70
  };
69
71
 
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.3.0",
4
+ "version": "0.4.1",
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",
@@ -56,6 +56,7 @@
56
56
  "src/conntrack.ts",
57
57
  "src/drops.ts",
58
58
  "src/eds.ts",
59
+ "src/lds.ts",
59
60
  "README.md",
60
61
  "LICENSE"
61
62
  ],
@@ -63,7 +64,7 @@
63
64
  "node": ">=22.19"
64
65
  },
65
66
  "dependencies": {
66
- "@mark1russell7/harness": "^0.12.2",
67
+ "@mark1russell7/harness": "^0.12.3",
67
68
  "@mark1russell7/quantity": "^0.2.0",
68
69
  "@mark1russell7/service": "^0.5.8",
69
70
  "testcontainers": "12.2.0"
package/src/lds.ts ADDED
@@ -0,0 +1,31 @@
1
+ // This module has no imports: the agent of the lab image imports it as it is.
2
+
3
+ /**
4
+ * This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS
5
+ * moves (`/stats?filter=`, `source/server/admin/stats_params.cc` of Envoy v1.39.4). An update of a
6
+ * listener counts `listener_modified`. An update in place, of its filter chains only, also counts
7
+ * `listener_in_place_updated` (`listener_manager_impl.cc:644`, `:692`).
8
+ */
9
+ export const LDS_FILTER = "^listener_manager\\.listener_(in_place_updated|modified)$";
10
+
11
+ /** The listener counts are the two counters of LDS of one sidecar: each update (`mod`), and each update in place (`inPlace`). */
12
+ export interface ListenerCounts {
13
+ readonly mod: number;
14
+ readonly inPlace: number;
15
+ }
16
+
17
+ /**
18
+ * This function gives the listener counts of the text of `/stats`, one `name: value` on each line
19
+ * (`stats_render.cc:15`). It gives null when the text has no counter `listener_modified`.
20
+ */
21
+ export const listenerCountsOf = (text: string): ListenerCounts | null => {
22
+ let mod: number | null = null;
23
+ let inPlace = 0;
24
+ for (const line of text.split("\n")) {
25
+ const match = /^listener_manager\.listener_(in_place_updated|modified): (\d+)$/.exec(line.trim());
26
+ if (match === null) continue;
27
+ if (match[1] === "modified") mod = Number(match[2]);
28
+ else inPlace = Number(match[2]);
29
+ }
30
+ return mod === null ? null : { mod, inPlace };
31
+ };