@mark1russell7/kind 0.2.3 → 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 CHANGED
@@ -65,6 +65,7 @@ The tests read three real records of `/1` (`test/records/`): a cell that holds,
65
65
  - **The scenario** (`scenario.ts`) is the cluster as data: its nodes, its version, the mode and the sync periods of kube-proxy, its CNI and its subnets. `kindConfig(scenario)` generates the configuration of kind from it.
66
66
  - **The node image** is `kindest/node:v1.37.0`, pinned by digest, the default of kind v0.33.0. The research names v1.37.1 for some defaults. The patch release is the mismatch.
67
67
  - **The tools** (`tools.ts`) are kind v0.33.0 and kubectl v1.37.0. The lab downloads each one into `.cache/` in this package, and it compares the sha256 with the pinned value at each use. `KIND_CACHE` names another folder. The lab installs nothing on the system, and the same code works on Linux, macOS and Windows.
68
+ - **istioctl 1.31.3** (`ISTIOCTL`) comes in a gzipped tar file. The pinned sha256 is that of the tar file. The cache keeps the tar file, and each use examines it and takes the tool from it again (`tarEntry`). The release has a zip file for Windows, thus Windows has no asset of istioctl.
68
69
  - **The isolation.** Each cluster gets its own Docker network, its own kubeconfig and its own cache for kubectl, in its run folder. The lab refuses a pod or service subnet that overlaps a Docker network.
69
70
 
70
71
  ## The lab
@@ -81,8 +82,17 @@ The tests read three real records of `/1` (`test/records/`): a cell that holds,
81
82
  - Each second, the agent reads the counters of dropped packets (`drops.ts`), and it writes a `drops` line with each counter that grew. For the node, these are the drops of the backlogs, the squeezes of the softirqs, and the drops of each veth.
82
83
  - For each client pod, the agent reads `/proc/<pid>/net`. These counters are the IP discards, the sends with no route, the failed connects, the drops of its interfaces, and its neighbors with no address. From 0.1.5, they also count the ICMP errors of an unreachable destination. A refusal by a reject rule is such an error, not a reset. A SYN that does not get to the conntrack of the node leaves its trace in one of them.
83
84
  - **The rules of each node.** The agent reads the rules of kube-proxy in the namespaces of the node each 50 ms: `iptables-save`, `nft list table ip kube-proxy`, or `/proc/net/ip_vs`. It writes a line when the backends of the Service change (`backends.ts`). A backend of IPVS with the weight zero takes no new connection, thus it is not a backend.
85
+ - **The endpoints of each client sidecar.** From 0.3.0, in a sidecar mesh, the agent also polls the Envoy of the client sidecar on its node each 50 ms (`eds`). The client sidecar picks the backend of each call, thus these endpoints and not the rules are the propagation of the cell (`propagationSource`).
86
+ - A poller enters the network namespace of the client pod (`nsenter -n`). It reads `/clusters` on `127.0.0.1:15000` with a filter that selects the server cluster (`eds.ts`).
87
+ - Each change of the healthy hosts gives an `eds` line with the start of the poll before it. That line has the form of a rules line. Each second, an `eds-alive` line counts the polls and gives their mean time.
88
+ - **The listeners of each server sidecar.** From 0.4.0, the agent also polls the Envoy of each server sidecar on its node each 50 ms (`lds`). It does this for a policy in a sidecar mesh (`pollsListeners`).
89
+ - A poller enters the network namespace of the server pod through the PID of its app. It reads `/stats` on `127.0.0.1:15000` with a filter that selects two counters of the listener manager (`lds.ts`).
90
+ - `listener_modified` counts each update of a listener, and `listener_in_place_updated` counts each update of its filter chains only.
91
+ - Each change of the counters gives an `lds` line with the start of the poll before it. The first line of a poller has no poll before it, thus it is no update (`updatesOf`).
84
92
  - **The injector of each node** is the image of the harness, in the PID namespace of the node. `crictl` on the node gives the PID of a container, and `nsenter -n` enters the network namespace of its pod. The injector of the harness then applies its rules there.
85
93
  - **The watcher** is a container on the network of the cluster. It watches the pods, the EndpointSlices and the events through the API, and it follows the log of each pod of the cell.
94
+ - From 0.3.0, it also follows the sidecar of Istio (`istio-proxy`), and a pod line gives the state of the sidecar (`sc`). Each line of the sidecar becomes a `proxy` line, stamped at its arrival. Its own stamps are of the wall clock, thus no band reads them.
95
+ - The access line of a call that succeeded at its first try is only counted, in a `proxy-ok` line each second. The record keeps each failure, each retry and each local reply, with its response flags and its upstream host.
86
96
  - **The prober** of the harness watches the network namespace of each pod, by its inode. The lab keeps its resets, errors and retransmissions, and it counts the other records.
87
97
 
88
98
  Each pod of a cluster shares the kernel of the Docker VM. Thus each stamp of the agent, the injector, the watcher, the clients, the service and the prober is on the same CLOCK_MONOTONIC.
@@ -104,6 +114,21 @@ From 0.2.0, a cell names its stack (`stack`): its server and its clients. A cell
104
114
 
105
115
  A declaration that can be null changes the type `KindCell`. That is a breaking change before 1.0.0, thus the version is 0.2.0.
106
116
 
117
+ ## The mesh
118
+
119
+ From 0.3.0, a cell can name a mesh (`mesh`): Istio 1.31.3 in its cluster. This is the template `T15`, the lifecycle of T07 through two mesh hops.
120
+
121
+ - **The field.** `mesh.flavor` is the install: `sidecar` is istiod only. `mesh.mode` is the data plane of the pods of the cell. The flavor `ambient` comes in step 3 of the plan, and the lab refuses it until then.
122
+ - **The cluster.** The cells of one kube-proxy mode, one node count and one mesh flavor share a cluster. `shardOf` keeps them together, and in each mode the cells with no mesh come first.
123
+ - **The install.** `installMesh` writes the IstioOperator of the flavor (`istioOperator`), and `istioctl install` applies it with the kubeconfig of the cluster. Then the lab waits for istiod.
124
+ - **The values.** The profile is `minimal`, because the profile `default` also has an ingress gateway. The pod of istiod requests 256 MiB, with one replica and no autoscaler. The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).
125
+ - **The images.** `ISTIO_IMAGES` pins istiod and the proxy by the digest of their index on Docker Hub. The lab pulls each one by its digest and tags it with that digest (`localPin`). Then `kind load` puts it on each node.
126
+ - **No pull.** Each image is a full reference, thus the charts use it as it is. The init container of the injector gets the image of the proxy too. `imagePullPolicy` is `Never`, thus an image that the lab did not load fails the install.
127
+ - **The namespace.** In the sidecar mode, the namespace of the cell has the label `istio-injection=enabled` (`namespaceObject`). The injector then puts a native sidecar in each pod: an init container that runs beside the app, because each kubelet is at least 1.33.
128
+ - **The probes.** The injector changes the gRPC probe of grpc-go into an HTTP probe of pilot-agent. Then pilot-agent sends the gRPC check to the app, with the service of the probe.
129
+
130
+ A new member of the union `Template` breaks a reader that switches on it. That is a breaking change before 1.0.0, thus the version is 0.3.0. The record format stays `/2`, with the optional field `mesh`. A judge before 0.3.0 cannot read a record of T15.
131
+
107
132
  ## The image
108
133
 
109
134
  The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`, its clients, the agent and the watcher, on Node 24.21.0. Its base is pinned by digest, and `npm-lock.json` pins the packages from npm. `stageLabImage()` stages its context, and `labImage()` tags the image by its content.
@@ -136,6 +161,7 @@ The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`
136
161
  | `app-stall` | the event loop of the first replica blocked |
137
162
  | `rollout` | no Redis pod, then a rolling update |
138
163
  | `cpu-throttle` | `cpu.max` in the cgroup of the container of the first replica |
164
+ | `authorization-policy` | `kubectl apply` of a DENY AuthorizationPolicy on the server pods for the principal of an account (`account`, the account `default` of the clients when not given), then `kubectl delete` after the hold |
139
165
 
140
166
  A cgroup path that is not the scope of one container is refused. A write to the root of the node throttles or freezes each pod of the node.
141
167
 
@@ -176,19 +202,29 @@ From 0.2.3, a window can also give the propagation that it assumes (`propagation
176
202
  - An earlier drop only closes the window earlier, thus the judge does not test the start of the band.
177
203
  - `predictionText` adds `the rules drop the replica in` and the band.
178
204
 
205
+ From 0.4.0, a window can count from the heal (`heal`), the first act that ends the fault. A window can also name its outcomes (`outcomes`). A failure in the window with another outcome then fails the run.
206
+
207
+ - The client of gRPC gives the outcome `denied` to PERMISSION_DENIED: the local reply 403 of an RBAC deny of a sidecar.
208
+ - For a policy, the propagation of a window is the LDS update of each server sidecar, after the apply and after the delete (`updatesOf`). An update after the band agrees with the law, and the cell is `extend`.
209
+ - A denial after the window, before the last update after the delete and one more call, is a `tail`. A denied call has no pod, because the sidecar answers it. Thus the judge reads the last update of each server sidecar.
210
+ - `calibrationOf` gives the updates after the apply by node, and the updates after the delete (`heal`).
211
+
212
+ A new member of the union `TriggerKind` breaks a reader that switches on it. That is a breaking change before 1.0.0, thus the version is 0.4.0. The record format stays `/2`, with the optional fields `account`, `outcomes`, `updates` and `healUpdates`, and the reference `heal`.
213
+
179
214
  A preStop sleep that is longer than the grace is a command in the container (`sh -c "sleep 60"`). The API server refuses a sleep action that is longer than the grace.
180
215
 
181
216
  ## Tests
182
217
 
183
218
  `pnpm --filter @mark1russell7/kind test` starts the tests of the configuration, the tools, the parser of the rules and the fault mapping. It also starts the tests of the predictions, the verdict and the judge of the real records. They do not need Docker.
184
219
 
185
- The catalogs are in `@systems/cells` (`t07.ts`, `t18.ts`). `KIND_LAB=1` starts them in kind.
220
+ The catalogs are in `@systems/cells` (`t07.ts`, `t18.ts`, `grpc/t07.ts` and `grpc/t15.ts`). `KIND_LAB=1` starts them in kind.
186
221
 
187
222
  ## CI
188
223
 
189
224
  The cells operate on the runners of GitHub, and not on a machine that does other work (`.github/workflows/kind.yml`). Each shard gets a runner, and each job makes its own clusters and deletes them.
190
225
 
191
226
  - **The modes of the kernel.** kube-proxy in a node container uses the modules of the host kernel. Thus the job loads `ip_vs` and `nf_tables` first. A cell of a mode that the kernel cannot do waits, and `summary.ndjson` tells why.
227
+ - **The inotify limits.** The pods of each node share the inotify limits of the kernel of the runner. Thus the job raises them to 512 instances and 524288 watches, the values of the known issues of kind.
192
228
  - **The shards.** `shardOf` gives each shard an equal share of the estimated time, and the cells of one mode stay together. Thus a shard makes few clusters.
193
229
  - **The output.** Each job uploads its raw records, `summary.ndjson`, and what the runner is. The run folder of each cluster stays out of the output, because a kubeconfig holds credentials.
194
230
  - **A pull request** that changes the workflow starts a smoke run: the calibration of each mode, with 2 runs on one cluster.
package/build/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as Line, $t as Workload, A as TriggerV1, At as LOG_LABEL, B as RunCheck, Bt as KindCell, C as KindCellResult, Ct as sliceDropOf, D as PredictionV1, Dt as transitionOf, E as LoadSample, Et as stoppingOf, F as KIND_NOISE, Ft as seconds, G as judgeKind, Gt as RedisDeclaration, H as checkRun, Ht as KubeletProbe, I as KindDecision, It as secondsOf, J as propagationOf, Jt as ResetReuse, K as observationAfter, Kt as Reference, L as KindVerdict, Lt as showBand, M as upgrade, Mt as REDIS, N as upgradeCell, Nt as SERVER, O as RunCheckV1, Ot as CLIENT, P as Deleted, Pt as bandOf, Q as Endpoint, Qt as TriggerKind, R as LAST_SLACK, Rt as Band, S as KindCellRecordV1, St as signalsOf, T as KindVerdictV1, Tt as slicesOf, U as deletionsOf, Ut as PodSettings, V as RunFacts, Vt as KindRun, W as factsOf, Wt as Prediction, X as reusesReset, Xt as Template, Y as referenceOf, Yt as Stack, Z as unitOf, Zt as Trigger, _ as ClusterManifest, _t as recordsOf, a as STAGES, an as NodeImage, at as backendsAt, b as FORMAT_V1, bt as rulesOf, c as readRecord, cn as goDuration, ct as deletionOf, d as calibrationOf, dn as nodeNames, dt as failuresOf, en as KUBE_PROXY_MODES, et as PodLine, f as cellsTable, fn as scenario, ft as intakeOf, g as BandV1, gt as podsOf, h as timeSpread, ht as podsAt, i as quantileOf, in as NODE_IMAGES, it as SliceLine, j as judge, jt as PORT, k as RunFactsV1, kt as HEALTH_PORT, l as Judged, ln as kindConfig, lt as dropOf, m as predictionText, mt as outcomesOf, n as medianInterval, nn as KindScenario, nt as Request, o as kindCellRecordSchema, on as cidrOverlap, ot as byClients, p as medianText, pn as scenarioProblems, pt as kernelCounts, q as observationEnd, qt as Replica, r as middle, rn as KubeProxyMode, rt as RulesLine, s as kindCellRecordV1Schema, sn as cidrRange, st as byNode, t as RELATIVE, tn as KindConfig, tt as RecordLine, u as TimeSpread, un as kubeProxyPatch, ut as emptyOf, v as EnvironmentManifest, vt as requestsOf, w as KindCellV1, wt as sliceEmptyOf, x as KindCellRecord, xt as signalOf, y as FORMAT, yt as restartsAt, z as Propagated, zt as Declaration } from "./judge-CctoUEo8.js";
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, _n as nodeNames, _t as outcomesOf, a as STAGES, an as Workload, at as Request, b as FORMAT_V1, bt as recordsOf, c as readRecord, cn as KindScenario, ct as backendsAt, d as calibrationOf, dn as NodeImage, dt as deletionOf, en as ResetReuse, et as Endpoint, f as cellsTable, fn as cidrOverlap, ft as dropOf, g as BandV1, gn as kubeProxyPatch, gt as kernelCounts, h as timeSpread, hn as kindConfig, 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, mn as goDuration, mt as failuresOf, n as medianInterval, nn as Template, nt as PodLine, o as kindCellRecordSchema, on as KUBE_PROXY_MODES, ot as RulesLine, p as medianText, pn as cidrRange, pt as emptyOf, q as observationEnd, qt as MeshFlavor, r as middle, rn as Trigger, rt as PropagationSource, s as kindCellRecordV1Schema, sn as KindConfig, st as SliceLine, t as RELATIVE, tn as Stack, tt as Line, u as TimeSpread, un as NODE_IMAGES, ut as byNode, v as EnvironmentManifest, vn as scenario, vt as podsAt, w as KindCellV1, wt as signalOf, x as KindCellRecord, xt as requestsOf, y as FORMAT, yn as scenarioProblems, yt as podsOf, z as Propagated, zt as secondsOf } from "./judge-DMuI3aDn.js";
2
2
  import { Quantity } from "@mark1russell7/quantity";
3
3
  import { CheckFn, Finding, HealthServer, Poll, Rule, Scenario, ServiceBuilder, ServiceDescription, Stage, Timing, TransportImpl, Verdict } from "@mark1russell7/service";
4
4
  import { RedisClient } from "@mark1russell7/service/redis";
@@ -77,6 +77,29 @@ export declare const WATCHDOG = "self.event-loop";
77
77
  */
78
78
  export declare const compose: (name: string, declaration: Declaration, parts?: Parts) => ServiceBuilder;
79
79
  //#endregion
80
+ //#region src/eds.d.ts
81
+ /**
82
+ * These are the hosts of a cluster: the healthy ones (`b`, in sequence), and each other host with its
83
+ * health flags (`u`). Each host is `ip:port`.
84
+ */
85
+ interface ClusterHosts {
86
+ readonly b: readonly string[];
87
+ readonly u: Readonly<Record<string, string>>;
88
+ }
89
+ /**
90
+ * This function gives the name of the outbound cluster of a Service port in a sidecar of Istio:
91
+ * `outbound|<port>||<service>.<namespace>.svc.cluster.local`.
92
+ */
93
+ export declare const outboundCluster: (service: string, namespace: string, port: number) => string;
94
+ /** This function gives the filter of `/clusters` that selects one cluster: a regular expression of RE2 with anchors. */
95
+ export declare const clusterFilter: (cluster: string) => string;
96
+ /**
97
+ * This function gives the hosts of a cluster from the text of `/clusters`. Each host of the text has
98
+ * a line `<cluster>::<ip:port>::health_flags::<flags>`. The flags are `healthy`, or the names of the
99
+ * flags that the host has (`clusters_handler.cc` and `host_utility.cc` of Envoy v1.39.4).
100
+ */
101
+ export declare const hostsOf: (text: string, cluster: string) => ClusterHosts;
102
+ //#endregion
80
103
  //#region src/faults.d.ts
81
104
  /**
82
105
  * This module maps each trigger of a cell to the acts of the lab, as data. The runner finds each
@@ -122,6 +145,9 @@ type Step = {
122
145
  } | {
123
146
  readonly do: "wait";
124
147
  readonly time: Quantity;
148
+ } | {
149
+ readonly do: "apply";
150
+ readonly object: Readonly<Record<string, unknown>>;
125
151
  };
126
152
  /**
127
153
  * A fault plan is the steps of a run:
@@ -135,6 +161,17 @@ interface FaultPlan {
135
161
  readonly heal: readonly Step[];
136
162
  readonly restore: readonly Step[];
137
163
  }
164
+ /** This name is the name of the AuthorizationPolicy of the trigger `authorization-policy`. */
165
+ export declare const POLICY = "lab-deny";
166
+ /** This service account is the account of the clients: their pods name no other account. */
167
+ export declare const CLIENT_ACCOUNT = "default";
168
+ /**
169
+ * This function gives the AuthorizationPolicy of the trigger `authorization-policy`. It denies each
170
+ * request to the server pods from the principal of the service account `account`. Istio gives a
171
+ * pod of the mesh the principal `cluster.local/ns/<namespace>/sa/<account>` of its account. The
172
+ * RBAC filter of the server sidecar reads the principal of the peer from its mTLS certificate.
173
+ */
174
+ export declare const policyObject: (account: string) => Readonly<Record<string, unknown>>;
138
175
  /** This function gives the fault plan of a cell. */
139
176
  export declare const planOf: (cell: KindCell) => FaultPlan;
140
177
  /** This function tells if a trigger stops a pod of the service with its grace: a deletion, an eviction, a scale-in or a rollout. */
@@ -150,6 +187,66 @@ export declare const stageLabImage: () => string;
150
187
  /** This function builds the lab image one time for each process, and gives its tag. Docker keeps its layers between builds. */
151
188
  export declare const labImage: () => Promise<string>;
152
189
  //#endregion
190
+ //#region src/istio.d.ts
191
+ /**
192
+ * This module installs Istio 1.31.3 in a kind cluster with istioctl, from images pinned by digest.
193
+ * The release notes of 1.31.2 and 1.31.3 change no default that a cell of T15 reads. Each value
194
+ * of the install that is not a default of Istio is in `istioOperator`, with its reason.
195
+ */
196
+ /** This version is the release of Istio of the lab: the tag of istioctl and of each image. */
197
+ export declare const ISTIO_VERSION = "1.31.3";
198
+ /**
199
+ * These are the images of the flavor `sidecar`, pinned by the digest of their index on Docker Hub
200
+ * (read on 2026-10-24). `gcr.io/istio-release` has no final tag of 1.31. The lab pulls each one by
201
+ * its digest, tags it with that digest (`localPin`), and loads it into each node. Thus no node pulls
202
+ * from a registry.
203
+ */
204
+ export declare const ISTIO_IMAGES: {
205
+ readonly pilot: string;
206
+ readonly proxy: string;
207
+ };
208
+ /**
209
+ * This format is the JSON access log of each sidecar. Each field is a command of the formatter of
210
+ * Envoy v1.39.4. The fields give the status, the response flags and their details, and the gRPC
211
+ * status as a number. They also give the tries of the router, the upstream host and cluster, and
212
+ * the reasons of a failed connection. The watcher reads `code`, `flags`, `grpc` and `attempts`
213
+ * (`image/watch.mjs`).
214
+ */
215
+ export declare const ACCESS_LOG_FORMAT: Readonly<Record<string, string>>;
216
+ /** These are the local tags of the images, after the pull by digest. */
217
+ interface MeshImages {
218
+ readonly pilot: string;
219
+ readonly proxy: string;
220
+ }
221
+ /**
222
+ * This function gives the IstioOperator of a flavor. The values that are not the defaults of Istio:
223
+ * - The profile `minimal` is istiod only. The profile `default` also has an ingress gateway, which no cell uses.
224
+ * - Each image is a full reference, thus the charts use it as it is (`contains "/"` in the
225
+ * templates of istiod and of the injector). The init container of the injector has the image of the proxy too (`proxy_init`).
226
+ * - `imagePullPolicy: Never`: each image is on each node. An image that the lab did not load fails the install at once.
227
+ * - istiod requests 256 MiB, not 2 GiB, and has one replica with no autoscaler. Each node of kind
228
+ * reports the whole runner, and no cell scales istiod.
229
+ * - The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).
230
+ *
231
+ * The flavor `ambient` comes in a later step, thus it is refused.
232
+ */
233
+ export declare const istioOperator: (flavor: MeshFlavor, images: MeshImages) => Record<string, unknown>;
234
+ /**
235
+ * This function installs the mesh of a flavor in a cluster with `istioctl install` and the operator
236
+ * of the flavor. Then it waits for istiod. The kubeconfig of the cluster goes to istioctl.
237
+ */
238
+ export declare const installMesh: (cluster: Cluster, flavor: MeshFlavor, images: MeshImages) => Promise<void>;
239
+ /** This function gives the key of the mesh of a cell for its cluster: its flavor, or `none`. */
240
+ export declare const meshKey: (cell: Pick<KindCell, "mesh">) => MeshFlavor | "none";
241
+ /** This function gives the outbound cluster of the server of a cell in each client sidecar: the port 80 of its Service. */
242
+ export declare const serverCluster: (namespace: string) => string;
243
+ /** This time is the interval of the EDS poll of each client sidecar, the same as the poll of the rules. */
244
+ export declare const EDS_EVERY: Quantity;
245
+ /** This time is the interval of the LDS poll of each server sidecar, the same as the poll of EDS. */
246
+ export declare const LDS_EVERY: Quantity;
247
+ /** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */
248
+ export declare const pollsListeners: (cell: Pick<KindCell, "mesh" | "trigger">) => boolean;
249
+ //#endregion
153
250
  //#region src/lab.d.ts
154
251
  /** A stamp brackets an act on CLOCK_MONOTONIC, in milliseconds. */
155
252
  interface Stamp {
@@ -158,6 +255,25 @@ interface Stamp {
158
255
  }
159
256
  /** These kinds of kernel record stay in the raw record of a run. The lab counts the other kinds and drops them. */
160
257
  export declare const KEPT_KERNEL: ReadonlySet<string>;
258
+ /** An EDS spec is the cluster that the EDS pollers read, their interval, and each client pod with its node and a PID in it. */
259
+ interface EdsSpec {
260
+ readonly cluster: string;
261
+ readonly every: Quantity;
262
+ readonly pods: ReadonlyArray<{
263
+ readonly node: string;
264
+ readonly pod: string;
265
+ readonly pid: number;
266
+ }>;
267
+ }
268
+ /** An LDS spec is the interval of the LDS pollers, and each server pod with its node and a PID in it. */
269
+ interface LdsSpec {
270
+ readonly every: Quantity;
271
+ readonly pods: ReadonlyArray<{
272
+ readonly node: string;
273
+ readonly pod: string;
274
+ readonly pid: number;
275
+ }>;
276
+ }
161
277
  /** A kind lab is the instruments of one cluster: a sidecar pair on each node, the watcher of the API and the prober. */
162
278
  interface KindLab {
163
279
  readonly cluster: Cluster;
@@ -179,6 +295,17 @@ interface KindLab {
179
295
  rules(rule: (ServiceRule & {
180
296
  readonly every: Quantity;
181
297
  }) | null): Promise<void>;
298
+ /**
299
+ * This method asks each agent to poll the sidecar of each client pod of its node, `every` apart.
300
+ * Each poll gives the hosts of one cluster. With null, the polls stop. A pod gives the PID of one
301
+ * of its processes, and the agent enters the network namespace of that PID.
302
+ */
303
+ eds(spec: EdsSpec | null): Promise<void>;
304
+ /**
305
+ * This method asks each agent to poll the sidecar of each server pod of its node, `every` apart.
306
+ * Each poll gives the counters of LDS updates of that sidecar (`lds.ts`). With null, the polls stop.
307
+ */
308
+ lds(spec: LdsSpec | null): Promise<void>;
182
309
  act(node: string, spec: Readonly<Record<string, unknown>>): Promise<Stamp>;
183
310
  inject(node: string, pid: number, args: readonly string[]): Promise<Stamp>;
184
311
  now(): Promise<number>;
@@ -204,6 +331,25 @@ export declare const openKindLab: (cluster: Cluster, image: string) => Promise<K
204
331
  /** This function pins a Docker image by digest under a local tag that holds the digest, for `kind load` and `imagePullPolicy: Never`. */
205
332
  export declare const localPin: (image: string) => Promise<string>;
206
333
  //#endregion
334
+ //#region src/lds.d.ts
335
+ /**
336
+ * This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS
337
+ * moves (`/stats?filter=`, `source/server/admin/stats_params.cc` of Envoy v1.39.4). An update of a
338
+ * listener counts `listener_modified`. An update in place, of its filter chains only, also counts
339
+ * `listener_in_place_updated` (`listener_manager_impl.cc:644`, `:692`).
340
+ */
341
+ export declare const LDS_FILTER = "^listener_manager\\.listener_(in_place_updated|modified)$";
342
+ /** The listener counts are the two counters of LDS of one sidecar: each update (`mod`), and each update in place (`inPlace`). */
343
+ interface ListenerCounts {
344
+ readonly mod: number;
345
+ readonly inPlace: number;
346
+ }
347
+ /**
348
+ * This function gives the listener counts of the text of `/stats`, one `name: value` on each line
349
+ * (`stats_render.cc:15`). It gives null when the text has no counter `listener_modified`.
350
+ */
351
+ export declare const listenerCountsOf: (text: string) => ListenerCounts | null;
352
+ //#endregion
207
353
  //#region src/manifests.d.ts
208
354
  /**
209
355
  * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the
@@ -217,6 +363,11 @@ export declare const stackOf: (cell: Pick<KindCell, "stack">) => Stack;
217
363
  export declare const REDIS_IMAGE = "redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f";
218
364
  /** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */
219
365
  export declare const namespaceOf: (cell: Pick<KindCell, "name">) => string;
366
+ /**
367
+ * This function gives the Namespace object of a cell. In the sidecar mode of a mesh, the label
368
+ * `istio-injection=enabled` makes the injector of Istio put a sidecar in each pod of the namespace.
369
+ */
370
+ export declare const namespaceObject: (cell: Pick<KindCell, "mesh">, namespace: string) => Record<string, unknown>;
220
371
  /**
221
372
  * This function gives a probe of the pod, with each field of the cell. The probe of the service is an
222
373
  * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the
@@ -379,7 +530,8 @@ export declare const runKindCells: (cells: readonly KindCell[], options: RunOpti
379
530
  export declare const estimatedTime: (cell: KindCell, environments?: number) => Quantity;
380
531
  /**
381
532
  * This function gives the share of a shard of a catalog: cells in sequence. Thus the cells of one
382
- * mode stay together and share clusters. Each shard gets an equal share of the estimated time.
533
+ * mode and one mesh flavor stay together and share clusters. Each shard gets an equal share of the
534
+ * estimated time. In one mode, the cells with no mesh come first, in the sequence of the catalog.
383
535
  */
384
536
  export declare const shardOf: <T extends KindCell>(cells: readonly T[], index: number, count: number) => T[];
385
537
  //#endregion
@@ -388,20 +540,38 @@ export declare const shardOf: <T extends KindCell>(cells: readonly T[], index: n
388
540
  * A tool is a binary that the lab downloads, at one pinned release, for each platform. The lab
389
541
  * keeps it in the cache folder of this package and does not install it on the system. Each use
390
542
  * compares its sha256 with the pinned value.
543
+ *
544
+ * A tool with an `entry` comes in a gzipped tar file. The pinned sha256 is the sha256 of that file.
545
+ * The cache keeps the file, and each use takes the entry from it again.
391
546
  */
392
547
  interface Tool {
393
- readonly name: "kind" | "kubectl";
548
+ readonly name: "kind" | "kubectl" | "istioctl";
394
549
  readonly version: string;
395
550
  /** These are the release assets, by the platform and the architecture of Node: `win32-x64`, `linux-x64`. */
396
551
  readonly assets: Readonly<Record<string, {
397
552
  readonly url: string;
398
553
  readonly sha256: string;
399
554
  }>>;
555
+ /** This name is the file of the tool in its gzipped tar file, or nothing for a tool that is its own asset. */
556
+ readonly entry?: string | undefined;
400
557
  }
401
558
  /** This release of kind is v0.33.0, from 2026-08-26. Its default node image is Kubernetes 1.37.0. The sums are from its `.sha256sum` assets. */
402
559
  export declare const KIND: Tool;
403
560
  /** This release of kubectl is v1.37.0, the version of the node image. The sums are from the `.sha256` files of `dl.k8s.io`. */
404
561
  export declare const KUBECTL: Tool;
562
+ /**
563
+ * This release of istioctl is 1.31.3, from 2026-10-22. It holds the charts of its own version, thus the
564
+ * lab needs no other tool to install Istio. The sums are from the `.sha256` assets of the release. The
565
+ * release has a zip file for Windows, and the lab reads only a gzipped tar file. Thus Windows has no asset.
566
+ */
567
+ export declare const ISTIOCTL: Tool;
568
+ /**
569
+ * This function gives the bytes of one file of a gzipped tar file. A header of the ustar format has
570
+ * 512 bytes. It has the name at 0, the size in octal at 124, the type at 156 and the prefix of the
571
+ * name at 345. The data follows the header, in blocks of 512 bytes. The match is on the last part of the
572
+ * name. A link, a folder and an extended header are not a file, thus the search skips them.
573
+ */
574
+ export declare const tarEntry: (gzipped: Uint8Array, entry: string) => Uint8Array;
405
575
  /** This folder is the cache of the tools: `.cache/` in this package, which git ignores. `KIND_CACHE` names another folder. */
406
576
  export declare const cacheFolder: () => string;
407
577
  /** This function gives the platform key of this process, as the assets name it. */
@@ -412,9 +582,11 @@ export declare const sha256: (bytes: Uint8Array) => string;
412
582
  export declare const toolPath: (tool: Tool, key?: string, cache?: string) => string;
413
583
  /**
414
584
  * This function gives the path of a tool, after a download when the cache does not have it. It
415
- * refuses bytes whose sha256 is not the pinned value, from the cache or from the download.
585
+ * refuses bytes whose sha256 is not the pinned value, from the cache or from the download. For a
586
+ * tool in a tar file, the cache keeps the tar file beside the tool. Each use examines the tar file,
587
+ * and writes the tool again when its bytes are not those of the entry.
416
588
  */
417
589
  export declare const ensureTool: (tool: Tool, key?: string, cache?: string) => Promise<string>;
418
590
  //#endregion
419
- export { type Band, type BandV1, CLIENT, type Cluster, type ClusterManifest, type Credentials, type Declaration, type Deleted, type DeletionWindow, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type FaultPlan, HEALTH_PORT, type Judged, KIND_NOISE, KUBE_PROXY_MODES, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindConfig, type KindDecision, type KindLab, type KindRun, type KindScenario, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, NODE_IMAGES, type NodeImage, PORT, type Parts, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, type Propagation, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, type RunOptions, SERVER, STAGES, type ServiceRule, SliceLine, type Stack, type Stamp, type Step, type Target, type Template, type TimeSpread, type Tool, type Trigger, type TriggerKind, type TriggerV1, type Verdicts, type Workload, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, cidrOverlap, cidrRange, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, goDuration, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, medianInterval, medianText, middle, nodeNames, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, scenario, scenarioProblems, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stoppingOf, timeSpread, transitionOf, unitOf, upgrade, upgradeCell };
591
+ export { type Band, type BandV1, CLIENT, type Cluster, type ClusterHosts, type ClusterManifest, type Credentials, type Declaration, type Deleted, type DeletionWindow, type EdsSpec, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type FaultPlan, HEALTH_PORT, type Judged, KIND_NOISE, KUBE_PROXY_MODES, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindConfig, type KindDecision, type KindLab, type KindRun, type KindScenario, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, type LdsSpec, Line, type ListenerCounts, type LoadSample, type Mesh, type MeshFlavor, type MeshImages, type MeshMode, NODE_IMAGES, type NodeImage, PORT, type Parts, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, type Propagation, PropagationSource, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, type RunOptions, SERVER, STAGES, type ServiceRule, SliceLine, type Stack, type Stamp, type Step, type Target, type Template, type TimeSpread, type Tool, type Trigger, type TriggerKind, type TriggerV1, type Verdicts, type Workload, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, cidrOverlap, cidrRange, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, goDuration, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, medianInterval, medianText, middle, nodeNames, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, scenario, scenarioProblems, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stoppingOf, timeSpread, transitionOf, unitOf, updatesOf, upgrade, upgradeCell };
420
592
  //# sourceMappingURL=index.d.ts.map