@mark1russell7/kind 0.3.0 → 0.4.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 +14 -0
- package/build/index.d.ts +53 -2
- package/build/index.js +154 -25
- package/build/index.js.map +1 -1
- package/build/{judge-CRMYoEOw.js → judge-Bkbs7VVG.js} +121 -19
- package/build/judge-Bkbs7VVG.js.map +1 -0
- package/build/{judge-DWF4QdZ8.d.ts → judge-DMuI3aDn.d.ts} +31 -4
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/image/Dockerfile +1 -1
- package/image/agent.mjs +55 -24
- package/image/client-grpc.mjs +2 -0
- package/package.json +2 -1
- package/src/lds.ts +31 -0
- package/build/judge-CRMYoEOw.js.map +0 -1
|
@@ -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 {
|
|
853
|
-
//# sourceMappingURL=judge-
|
|
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
|
|
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 {
|
|
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
|
|
265
|
-
* pod of the node with a PID in it
|
|
266
|
-
* namespace of that PID, and stops each one when the
|
|
267
|
-
* mount namespace of the agent, thus the poller is this
|
|
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(
|
|
288
|
+
text = existsSync(file) ? readFileSync(file, "utf8") : null;
|
|
281
289
|
spec = text === null ? null : JSON.parse(text);
|
|
282
290
|
} catch (error) {
|
|
283
|
-
say({ ev:
|
|
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],
|
|
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:
|
|
302
|
+
if (signal !== "SIGKILL") say({ ev: `${kind}-exit`, t: now(), pod, code, signal });
|
|
295
303
|
});
|
|
296
|
-
child.on("error", (error) => say({ ev:
|
|
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:
|
|
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
|
|
308
|
-
* (envoy_bootstrap.json of Istio 1.31.3)
|
|
309
|
-
*
|
|
310
|
-
*
|
|
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
|
|
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
|
|
330
|
-
const key = JSON.stringify(
|
|
336
|
+
const state = read(text);
|
|
337
|
+
const key = JSON.stringify(state);
|
|
331
338
|
if (key !== last) {
|
|
332
|
-
say({ ev:
|
|
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:
|
|
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:
|
|
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 {
|
package/image/client-grpc.mjs
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "0.4.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",
|
|
@@ -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
|
],
|
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
|
+
};
|