@mark1russell7/kind 0.14.0 → 0.14.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.
- package/README.md +6 -0
- package/build/index.d.ts +79 -6
- package/build/index.js +161 -26
- package/build/index.js.map +1 -1
- package/build/{judge-Cp3MzMk1.d.ts → judge-DLn2PLxb.d.ts} +13 -2
- package/build/{judge-C6XgY2Pk.js → judge-DjtNaAVO.js} +41 -7
- package/build/{judge-C6XgY2Pk.js.map → judge-DjtNaAVO.js.map} +1 -1
- package/build/judge.d.ts +1 -1
- package/build/judge.js +1 -1
- package/image/agent.mjs +15 -0
- package/image/npm-lock.json +22 -0
- package/image/package.json +1 -0
- package/package.json +3 -3
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["whole","sleep","until","goDuration","sleep","KEPT","sleep","s","seconds","DRAIN_RULES","deletesAPod"],"sources":["../src/backends.ts","../src/scenario.ts","../src/tools.ts","../src/cluster.ts","../src/helm.ts","../src/cilium.ts","../src/compose.ts","../src/eds.ts","../src/faults.ts","../src/image.ts","../src/istio.ts","../src/kubevip.ts","../src/metallb.ts","../src/announcer.ts","../src/pulsar.ts","../src/lds.ts","../src/conntrack.ts","../src/lab.ts","../src/manifests.ts","../src/predict.ts","../src/rules.ts","../src/run.ts"],"sourcesContent":["// The parser of the rules of kube-proxy and of the services of Cilium. The agent of each node reads\n// the rules with it, and the image copies this file, thus it imports nothing.\n\n/** A service rule names the Service whose backends the agent reads: its namespace, its name, and its ClusterIP and port for IPVS and Cilium. */\nexport interface ServiceRule {\n readonly mode: \"iptables\" | \"ipvs\" | \"nftables\" | \"cilium\";\n readonly ns: string;\n readonly svc: string;\n readonly ip: string;\n readonly port: number;\n}\n\nconst hexIp = (hex: string): string =>\n [0, 2, 4, 6].map((at) => parseInt(hex.slice(at, at + 2), 16)).join(\".\");\n\n/**\n * This function gives the backends of a Service in a dump of the rules of kube-proxy, as\n * `ip:port`, sorted. A backend of IPVS with the weight zero takes no new connection, thus it is\n * not a backend.\n * - iptables: the `KUBE-SVC` chain of the Service jumps to a `KUBE-SEP` chain for each backend, and that chain does the DNAT.\n * - nftables: the `service-` chain of the Service does the DNAT to each backend, or it names an `endpoint-` chain for each one.\n * - IPVS: `/proc/net/ip_vs` has the line of the virtual service, then a line for each real server.\n * - Cilium: the JSON of the services of the agent (`ciliumBackendsOf`).\n */\nexport const backendsOf = (mode: ServiceRule[\"mode\"], text: string, rule: Pick<ServiceRule, \"ns\" | \"svc\" | \"ip\" | \"port\">): string[] => {\n if (mode === \"cilium\") return ciliumBackendsOf(text, rule);\n const out = new Set<string>();\n const lines = text.split(\"\\n\");\n if (mode === \"iptables\") {\n const seps = new Set<string>();\n for (const line of lines) {\n if (!line.startsWith(\"-A KUBE-SVC-\") || !line.includes(`${rule.ns}/${rule.svc}:`)) continue;\n const jump = / -j (KUBE-SEP-\\S+)/.exec(line);\n if (jump !== null) seps.add(jump[1]!);\n }\n for (const line of lines) {\n const chain = /^-A (KUBE-SEP-\\S+) /.exec(line);\n if (chain === null || !seps.has(chain[1]!)) continue;\n const to = / --to-destination (\\S+)/.exec(line);\n if (to !== null) out.add(to[1]!);\n }\n } else if (mode === \"nftables\") {\n // a chain name with a slash can print in quotes: `chain \"service-…\" {` and `goto \"endpoint-…\"`\n let inside = false;\n const ours = `-${rule.ns}/${rule.svc}/`;\n for (const line of lines) {\n const head = /^\\s*chain\\s+\"?([^\"\\s{]+)\"?\\s*\\{/.exec(line);\n if (head !== null) inside = head[1]!.startsWith(\"service-\") && head[1]!.includes(ours);\n else if (/^\\s*(set|map)\\s/.test(line)) inside = false;\n if (!inside) continue;\n // one chain for each endpoint, as kube-proxy 1.31 writes it\n for (const match of line.matchAll(/(?:goto|jump)\\s+\"?endpoint-[0-9A-Za-z]+(-[^\\s,}\"]+)__([0-9.]+)\\/(\\d+)\"?/g))\n if (match[1]!.startsWith(ours)) out.add(`${match[2]}:${match[3]}`);\n // a DNAT in the service chain, as kube-proxy 1.37 writes it: `numgen random mod 2 map { 0 : IP . PORT, … }`, or `dnat to IP:PORT`\n for (const match of line.matchAll(/(?:^|[\\s{,:])(\\d+\\.\\d+\\.\\d+\\.\\d+) \\. (\\d+)(?=\\s*[,}]|\\s*$)/g)) out.add(`${match[1]}:${match[2]}`);\n for (const match of line.matchAll(/\\bdnat (?:ip )?to (\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)/g)) out.add(`${match[1]}:${match[2]}`);\n }\n } else {\n let inside = false;\n for (const line of lines) {\n // each virtual service starts a block: a UDP or an SCTP one ends the block of the TCP service before it\n const service = /^(TCP|UDP|SCTP)\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s/.exec(line);\n if (service !== null) {\n inside = service[1] === \"TCP\" && hexIp(service[2]!) === rule.ip && parseInt(service[3]!, 16) === rule.port;\n continue;\n }\n const real = /^\\s+->\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s+\\S+\\s+(\\d+)/.exec(line);\n if (inside && real !== null && Number(real[3]) > 0) out.add(`${hexIp(real[1]!)}:${parseInt(real[2]!, 16)}`);\n }\n }\n return [...out].sort();\n};\n\n/** This is one service of the agent of Cilium, as `GET /v1/service` gives it: the fields that the parser reads. */\ninterface CiliumService {\n readonly spec?: {\n readonly \"frontend-address\"?: { readonly ip?: string; readonly port?: number; readonly protocol?: string; readonly scope?: string };\n readonly \"backend-addresses\"?: ReadonlyArray<{ readonly ip?: string; readonly port?: number; readonly state?: string }> | null;\n readonly flags?: { readonly type?: string; readonly name?: string; readonly namespace?: string };\n };\n}\n\n/** This function gives the services in a reply of the agent of Cilium. It refuses a reply that is not a JSON list. */\nconst ciliumServices = (text: string): CiliumService[] => {\n const data: unknown = JSON.parse(text);\n if (!Array.isArray(data)) throw new Error(\"the reply of the agent of Cilium is not a list of services\");\n return data as CiliumService[];\n};\n\n/**\n * This function gives the backends of a Service in the reply of `GET /v1/service` of the agent of\n * Cilium 1.20, as `ip:port`, sorted. The model is `ServiceSpec` of `api/v1/openapi.yaml`. The\n * frontend is the ClusterIP and the port of the rule, with the scope `external` or no scope.\n *\n * The backends are those in the state `active`, or with no state. If no backend is active, the\n * backends are those in the state `terminating`. The BPF reconciler gives the slots of a service\n * in this sequence (`pkg/loadbalancer/reconciler/bpf_reconciler.go`). Thus a new connection goes\n * only to them. kube-proxy does the same with the endpoints that serve and stop. The other\n * states take no new connection: `terminating-not-serving`, `quarantined` and `maintenance`.\n */\nexport const ciliumBackendsOf = (text: string, rule: Pick<ServiceRule, \"ip\" | \"port\">): string[] => {\n const ours = ciliumServices(text).filter((service) => {\n const front = service.spec?.[\"frontend-address\"];\n const protocol = (front?.protocol ?? \"tcp\").toLowerCase();\n return front?.ip === rule.ip && front.port === rule.port && (protocol === \"tcp\" || protocol === \"any\") && (front.scope ?? \"external\") === \"external\";\n });\n const backends = ours.flatMap((service) => service.spec?.[\"backend-addresses\"] ?? []);\n const of = (states: readonly (string | undefined)[]): string[] =>\n [...new Set(backends.filter((b) => states.includes(b.state) && b.ip !== undefined).map((b) => `${b.ip}:${b.port ?? 0}`))].sort();\n const active = of([\"active\", undefined]);\n return active.length > 0 ? active : of([\"terminating\"]);\n};\n\n/**\n * This function tells if a dump of the rules names the Service of a rule. The rules of kube-proxy\n * name it as `namespace/name`. The JSON of Cilium names it in the flags of its frontend.\n */\nexport const namesService = (mode: ServiceRule[\"mode\"], text: string, rule: Pick<ServiceRule, \"ns\" | \"svc\">): boolean => {\n if (mode !== \"cilium\") return text.includes(`${rule.ns}/${rule.svc}`);\n try {\n return ciliumServices(text).some((service) => service.spec?.flags?.namespace === rule.ns && service.spec.flags.name === rule.svc);\n } catch {\n return false;\n }\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\n\n/** A kube-proxy mode is the mode of kube-proxy on each node: iptables (the default of kind), IPVS or nftables. */\nexport type KubeProxyMode = \"iptables\" | \"ipvs\" | \"nftables\";\nexport const KUBE_PROXY_MODES: readonly KubeProxyMode[] = [\"iptables\", \"ipvs\", \"nftables\"];\n\n/**\n * A dataplane is the path of a Service on each node, and the mode of a cell. It is kube-proxy in one\n * of its modes, or `cilium`: Cilium is the CNI and replaces kube-proxy (`cilium.ts`).\n */\nexport type Dataplane = KubeProxyMode | \"cilium\";\nexport const DATAPLANES: readonly Dataplane[] = [...KUBE_PROXY_MODES, \"cilium\"];\n\n/** This function tells if a dataplane is a mode of kube-proxy. */\nexport const isKubeProxyMode = (mode: Dataplane): mode is KubeProxyMode => mode !== \"cilium\";\n\n/** A node image is the kind node image of one Kubernetes version, pinned by digest. */\nexport interface NodeImage {\n readonly kubernetes: string;\n readonly image: string;\n}\n\n/**\n * These are the node images of kind v0.33.0, from its release notes. The image of Kubernetes 1.37\n * is v1.37.0. The research names v1.37.1 for some defaults, thus a default that changed in the\n * patch release is a mismatch to record.\n */\nexport const NODE_IMAGES: Readonly<Record<string, NodeImage>> = {\n \"1.37\": {\n kubernetes: \"v1.37.0\",\n image: \"kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5\",\n },\n \"1.36\": {\n kubernetes: \"v1.36.4\",\n image: \"kindest/node:v1.36.4@sha256:099e049362a1526b2db71494e1947aae99bd16290d7c895f2b7ea312e3cbfaed\",\n },\n};\n\n/**\n * A scenario is one kind cluster as data. The lab generates the configuration of kind from it, and\n * nothing comes from a default of kind. The scenario gives the node count, the version, the\n * dataplane, the sync periods of kube-proxy, the CNI and the subnets.\n */\nexport interface KindScenario {\n /** This name names the cluster and its Docker network: lowercase letters, digits and dashes. */\n readonly name: string;\n /** This key selects the node image (`NODE_IMAGES`). */\n readonly kubernetes: string;\n /** This count is the number of workers. The cluster also has one control plane. */\n readonly workers: number;\n /** This dataplane is the path of a Service on each node. */\n readonly dataplane: Dataplane;\n /** This is kube-proxy and its sync periods, or null when Cilium replaces kube-proxy. */\n readonly kubeProxy: {\n readonly mode: KubeProxyMode;\n /** This time is the shortest interval between two syncs of the rules. */\n readonly minSyncPeriod: Quantity;\n /** This time is the interval of the full sync of the rules. */\n readonly syncPeriod: Quantity;\n } | null;\n /** This CNI is kindnet, the CNI of kind, beside kube-proxy. The CNI of the dataplane `cilium` is Cilium. */\n readonly cni: \"kindnet\" | \"cilium\";\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n /**\n * If this flag is true, each node has the label `topology.kubernetes.io/zone`. The last worker is\n * in the zone `ZONE_LOST`, and the other nodes are in the zone `ZONE_KEPT`.\n */\n readonly zoned?: boolean;\n}\n\n/** This label gives the zone of a node, as Kubernetes names it. */\nexport const ZONE_LABEL = \"topology.kubernetes.io/zone\";\n/** This zone holds the control plane and each worker but the last. A zone loss keeps it. */\nexport const ZONE_KEPT = \"zone-a\";\n/** This zone holds only the last worker. A zone loss loses it. */\nexport const ZONE_LOST = \"zone-b\";\n\n/** This function gives the zone of the node at the position `at` of `nodeNames`, in a cluster of `count` nodes. */\nexport const zoneOf = (at: number, count: number): string => (at === count - 1 && at > 0 ? ZONE_LOST : ZONE_KEPT);\n\n/**\n * This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values.\n * The dataplane `cilium` has no kube-proxy, and its CNI is Cilium.\n */\nexport const scenario = (name: string, mode: Dataplane, workers: number, kubernetes = \"1.37\", zoned = false): KindScenario => ({\n name,\n kubernetes,\n workers,\n dataplane: mode,\n kubeProxy: isKubeProxyMode(mode) ? { mode, minSyncPeriod: { value: 1, unit: \"s\" }, syncPeriod: { value: 30, unit: \"s\" } } : null,\n cni: isKubeProxyMode(mode) ? \"kindnet\" : \"cilium\",\n podSubnet: \"10.244.0.0/16\",\n serviceSubnet: \"10.96.0.0/16\",\n ...(zoned ? { zoned } : {}),\n});\n\nconst NAME = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;\n\n/**\n * This function gives the problems of a scenario. A problem is a name that kind refuses, no image for\n * the version, or a count out of range. A kube-proxy or a CNI that does not agree with the dataplane is a problem too.\n */\nexport const scenarioProblems = (s: KindScenario): string[] => {\n const problems: string[] = [];\n if (!NAME.test(s.name)) problems.push(`the name ${s.name} is not lowercase letters, digits and dashes of 1 to 32 characters`);\n if (NODE_IMAGES[s.kubernetes] === undefined) problems.push(`no node image is pinned for Kubernetes ${s.kubernetes}`);\n if (!Number.isInteger(s.workers) || s.workers < 0 || s.workers > 8) problems.push(`${s.workers} workers is not a whole number from 0 to 8`);\n if (cidrOverlap(s.podSubnet, s.serviceSubnet)) problems.push(`the pod subnet ${s.podSubnet} overlaps the service subnet ${s.serviceSubnet}`);\n if (isKubeProxyMode(s.dataplane)) {\n if (s.kubeProxy?.mode !== s.dataplane) problems.push(`the dataplane ${s.dataplane} needs kube-proxy in the mode ${s.dataplane}`);\n if (s.cni !== \"kindnet\") problems.push(`the dataplane ${s.dataplane} needs the CNI kindnet`);\n } else if (s.kubeProxy !== null || s.cni !== \"cilium\") problems.push(\"the dataplane cilium needs no kube-proxy and the CNI cilium\");\n return problems;\n};\n\n/** This function gives the names of the node containers, as kind names them: the control plane, then each worker. */\nexport const nodeNames = (s: KindScenario): string[] => [\n `${s.name}-control-plane`,\n ...Array.from({ length: s.workers }, (_, at) => `${s.name}-worker${at === 0 ? \"\" : at + 1}`),\n];\n\n/** This function gives a duration as kube-proxy reads it, in whole milliseconds: `1s`, `500ms`. */\nexport const goDuration = (amount: Quantity): string => {\n const ms = valueIn(amount, \"ms\");\n if (!Number.isInteger(ms) || ms < 0) throw new RangeError(`${ms} ms is not a whole number of milliseconds`);\n return ms % 1000 === 0 ? `${ms / 1000}s` : `${ms}ms`;\n};\n\n/** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. It refuses a scenario with no kube-proxy. */\nexport const kubeProxyPatch = (s: KindScenario): string => {\n const proxy = s.kubeProxy;\n if (proxy === null) throw new Error(`the scenario ${s.name} has no kube-proxy`);\n return [\n \"apiVersion: kubeproxy.config.k8s.io/v1alpha1\",\n \"kind: KubeProxyConfiguration\",\n `mode: ${proxy.mode}`,\n `${proxy.mode}:`,\n ` minSyncPeriod: ${goDuration(proxy.minSyncPeriod)}`,\n ` syncPeriod: ${goDuration(proxy.syncPeriod)}`,\n \"\",\n ].join(\"\\n\");\n};\n\n/** A kind configuration is the cluster file that `kind create cluster --config` reads. JSON is YAML, thus the lab writes it as JSON. */\nexport interface KindConfig {\n readonly kind: \"Cluster\";\n readonly apiVersion: \"kind.x-k8s.io/v1alpha4\";\n readonly networking: {\n readonly ipFamily: \"ipv4\";\n /** This mode is the mode of kube-proxy, or `none` for no kube-proxy. */\n readonly kubeProxyMode: KubeProxyMode | \"none\";\n readonly disableDefaultCNI: boolean;\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n };\n readonly nodes: ReadonlyArray<{\n readonly role: \"control-plane\" | \"worker\";\n readonly image: string;\n readonly labels: Readonly<Record<string, string>>;\n }>;\n readonly kubeadmConfigPatches: readonly string[];\n}\n\n/**\n * This function generates the configuration of kind from a scenario. It refuses a scenario with a\n * problem. The dataplane `cilium` gets no kindnet, as the kind guide of Cilium 1.20.4 tells\n * (`Documentation/installation/kind-configure.rst`). It also gets no kube-proxy, because Cilium\n * replaces it (`Documentation/network/kubernetes/kubeproxy-free.rst`).\n */\nexport const kindConfig = (s: KindScenario): KindConfig => {\n const problems = scenarioProblems(s);\n if (problems.length > 0) throw new Error(`the scenario ${s.name}: ${problems.join(\"; \")}`);\n const image = NODE_IMAGES[s.kubernetes]!.image;\n return {\n kind: \"Cluster\",\n apiVersion: \"kind.x-k8s.io/v1alpha4\",\n networking: {\n ipFamily: \"ipv4\",\n kubeProxyMode: s.kubeProxy?.mode ?? \"none\",\n disableDefaultCNI: s.cni !== \"kindnet\",\n podSubnet: s.podSubnet,\n serviceSubnet: s.serviceSubnet,\n },\n nodes: nodeNames(s).map((node, at, all) => ({\n role: at === 0 ? \"control-plane\" : \"worker\",\n image,\n labels: {\n \"lab.systems/node\": node.slice(s.name.length + 1),\n ...(s.zoned === true ? { [ZONE_LABEL]: zoneOf(at, all.length) } : {}),\n },\n })),\n kubeadmConfigPatches: s.kubeProxy === null ? [] : [kubeProxyPatch(s)],\n };\n};\n\n/** This function gives the first and the last address of an IPv4 CIDR, as numbers. */\nexport const cidrRange = (cidr: string): [number, number] => {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\/(\\d+)$/.exec(cidr);\n if (match === null) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const octets = match.slice(1, 5).map(Number);\n const bits = Number(match[5]);\n if (octets.some((octet) => octet > 255) || bits > 32) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const base = octets.reduce((sum, octet) => sum * 256 + octet, 0);\n const size = 2 ** (32 - bits);\n const first = Math.floor(base / size) * size;\n return [first, first + size - 1];\n};\n\n/** This function tells if two IPv4 CIDRs share an address. */\nexport const cidrOverlap = (a: string, b: string): boolean => {\n const [a0, a1] = cidrRange(a);\n const [b0, b1] = cidrRange(b);\n return a0 <= b1 && b0 <= a1;\n};\n","import { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { gunzipSync } from \"node:zlib\";\n\n/**\n * A tool is a binary that the lab downloads, at one pinned release, for each platform. The lab\n * keeps it in the cache folder of this package and does not install it on the system. Each use\n * compares its sha256 with the pinned value.\n *\n * A tool with an `entry` comes in a gzipped tar file. The pinned sha256 is the sha256 of that file.\n * The cache keeps the file, and each use takes the entry from it again.\n */\nexport interface Tool {\n readonly name: \"kind\" | \"kubectl\" | \"istioctl\" | \"helm\";\n readonly version: string;\n /** These are the release assets, by the platform and the architecture of Node: `win32-x64`, `linux-x64`. */\n readonly assets: Readonly<Record<string, { readonly url: string; readonly sha256: string }>>;\n /** This name is the file of the tool in its gzipped tar file, or nothing for a tool that is its own asset. */\n readonly entry?: string | undefined;\n}\n\n/** 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. */\nexport const KIND: Tool = {\n name: \"kind\",\n version: \"v0.33.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-windows-amd64\",\n sha256: \"4b22adaa135368c5a465d56bbd8e520cbea87272a06ca00b6078e7b81515c9fc\",\n },\n \"linux-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-amd64\",\n sha256: \"aee6151561422756b764a4ae28e7f44cda5af5a9eead3cc9985112b1de8d8e0d\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-arm64\",\n sha256: \"20022bee6cfcd5086cb7234d218e3454e6090022f2a8f55d1fa7fcf42c3867a2\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-darwin-arm64\",\n sha256: \"0c8c7dbe5e23594a198b786c4bc13dacc101fa6196b0cb0b23a1ca44e61f4b4f\",\n },\n },\n};\n\n/** 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`. */\nexport const KUBECTL: Tool = {\n name: \"kubectl\",\n version: \"v1.37.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/windows/amd64/kubectl.exe\",\n sha256: \"4721b614a67bb4932a0369e61f4a323d8c6ca00943d3a2ff14837c124da06f0e\",\n },\n \"linux-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/amd64/kubectl\",\n sha256: \"6129359f4e1f3848a5572ccb0b26cf28b8ca08cef38c95a765b2f64a2c961a2f\",\n },\n \"linux-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/arm64/kubectl\",\n sha256: \"922df28df248cc00a9e025f947704f1d1482de64ece54cfe57e61f19eaf1eef3\",\n },\n \"darwin-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/darwin/arm64/kubectl\",\n sha256: \"583beedaebe422e71d3f1a96acef8b1fef86ea2f09a45ad01aa6c9ce287c1380\",\n },\n },\n};\n\n/**\n * This release of istioctl is 1.31.3, from 2026-10-22. It holds the charts of its own version, thus the\n * lab needs no other tool to install Istio. The sums are from the `.sha256` assets of the release. The\n * release has a zip file for Windows, and the lab reads only a gzipped tar file. Thus Windows has no asset.\n */\nexport const ISTIOCTL: Tool = {\n name: \"istioctl\",\n version: \"1.31.3\",\n entry: \"istioctl\",\n assets: {\n \"linux-x64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-amd64.tar.gz\",\n sha256: \"382c3004ce2e9c5ed3064b092ae0da7f6bbe30c766a1be282f45725add004860\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-arm64.tar.gz\",\n sha256: \"7476801623dbb900073cb1dba6a23b6a15c564b7e92b208aed36829315fefff1\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-osx-arm64.tar.gz\",\n sha256: \"881c3b443d9e458e8f19feed6ffa187dcfcb03dc032cb56068f7d1d0cd976c82\",\n },\n },\n};\n\n/**\n * This release of Helm is v3.22.1, from 2026-10-15. The lab installs Cilium from its chart with it.\n * The guide of Cilium 1.20.4 installs with Helm 3. The sums are from the `.sha256sum` files of\n * `get.helm.sh`. The tar file for Windows has `helm.exe`, not `helm`, thus Windows has no asset.\n */\nexport const HELM: Tool = {\n name: \"helm\",\n version: \"v3.22.1\",\n entry: \"helm\",\n assets: {\n \"linux-x64\": {\n url: \"https://get.helm.sh/helm-v3.22.1-linux-amd64.tar.gz\",\n sha256: \"896cf63684a42a723d61c980369d6e588330927deb13676ca0d85006dd6da0ea\",\n },\n \"linux-arm64\": {\n url: \"https://get.helm.sh/helm-v3.22.1-linux-arm64.tar.gz\",\n sha256: \"4c29e286b279d170fa048ddfc3e4e83dcd069ca9c52102766ba672d7149e694d\",\n },\n \"darwin-arm64\": {\n url: \"https://get.helm.sh/helm-v3.22.1-darwin-arm64.tar.gz\",\n sha256: \"e73da8a0b0d5193774fdff17ce6319c7436bb22ee229b45fd64f3c40368c753a\",\n },\n },\n};\n\n/**\n * This function gives the bytes of one file of a gzipped tar file. A header of the ustar format has\n * 512 bytes. It has the name at 0, the size in octal at 124, the type at 156 and the prefix of the\n * name at 345. The data follows the header, in blocks of 512 bytes. The match is on the last part of the\n * name. A link, a folder and an extended header are not a file, thus the search skips them.\n */\nexport const tarEntry = (gzipped: Uint8Array, entry: string): Uint8Array => {\n const tar = gunzipSync(gzipped);\n const text = (from: number, length: number): string => {\n const field = tar.subarray(from, from + length);\n const end = field.indexOf(0);\n return field.subarray(0, end === -1 ? field.length : end).toString(\"latin1\");\n };\n let at = 0;\n while (at + 512 <= tar.length) {\n const name = text(at, 100);\n if (name === \"\") break;\n const size = Number.parseInt(text(at + 124, 12).trim() || \"0\", 8);\n if (!Number.isFinite(size)) throw new Error(`the tar entry ${name} has no size in octal`);\n const type = String.fromCharCode(tar[at + 156] ?? 0);\n const prefix = text(at + 345, 155);\n const full = prefix === \"\" ? name : `${prefix}/${name}`;\n if ((type === \"0\" || type === \"\\0\") && full.split(\"/\").at(-1) === entry) return new Uint8Array(tar.subarray(at + 512, at + 512 + size));\n at += 512 + Math.ceil(size / 512) * 512;\n }\n throw new Error(`the tar file has no file ${entry}`);\n};\n\n/** This folder is the cache of the tools: `.cache/` in this package, which git ignores. `KIND_CACHE` names another folder. */\nexport const cacheFolder = (): string =>\n process.env[\"KIND_CACHE\"] ?? fileURLToPath(new URL(\"../.cache/\", import.meta.url));\n\n/** This function gives the platform key of this process, as the assets name it. */\nexport const platformKey = (platform: string = process.platform, arch: string = process.arch): string =>\n `${platform}-${arch}`;\n\n/** This function gives the sha256 of some bytes, in hexadecimal. */\nexport const sha256 = (bytes: Uint8Array): string => createHash(\"sha256\").update(bytes).digest(\"hex\");\n\n/** This function gives the path of a tool in the cache, for a platform. */\nexport const toolPath = (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): string =>\n join(cache, \"tools\", `${tool.name}-${tool.version}-${key}`, key.startsWith(\"win32\") ? `${tool.name}.exe` : tool.name);\n\n/** This function writes a file through a partial file, thus a stop in the middle leaves no file at the path. */\nconst writeWhole = (path: string, bytes: Uint8Array, executable: boolean): void => {\n mkdirSync(dirname(path), { recursive: true });\n const partial = `${path}.partial`;\n writeFileSync(partial, bytes);\n if (executable) chmodSync(partial, 0o755);\n renameSync(partial, path);\n};\n\n/** These are the waits in ms before the second and the third try of a download. A runner can fail to connect for a moment. */\nexport const DOWNLOAD_WAITS: readonly number[] = [2_000, 8_000];\n\n/**\n * This function gives the bytes of a URL. It tries again after each wait when the connection fails or\n * the answer is a 5xx. It refuses each other answer that is not ok at once.\n */\nexport const download = async (url: string, waits: readonly number[] = DOWNLOAD_WAITS): Promise<Uint8Array> => {\n for (let attempt = 0; ; attempt++) {\n const response = await fetch(url, { redirect: \"follow\" }).catch((error: unknown) => (error instanceof Error ? error : new Error(String(error))));\n if (response instanceof Response && response.ok) return new Uint8Array(await response.arrayBuffer());\n const failure = response instanceof Response ? new Error(`the download of ${url} answered ${response.status}`) : response;\n const wait = waits[attempt];\n if ((response instanceof Response && response.status < 500) || wait === undefined) throw failure;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n};\n\n/**\n * This function gives the path of a tool, after a download when the cache does not have it. It\n * refuses bytes whose sha256 is not the pinned value, from the cache or from the download. For a\n * tool in a tar file, the cache keeps the tar file beside the tool. Each use examines the tar file,\n * and writes the tool again when its bytes are not those of the entry.\n */\nexport const ensureTool = async (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): Promise<string> => {\n const asset = tool.assets[key];\n if (asset === undefined) throw new Error(`${tool.name} ${tool.version} has no pinned asset for ${key}`);\n const path = toolPath(tool, key, cache);\n const executable = !key.startsWith(\"win32\");\n // the downloaded asset: the tool itself, or its tar file\n const kept = tool.entry === undefined ? path : `${path}.tar.gz`;\n let bytes: Uint8Array;\n if (existsSync(kept)) {\n bytes = readFileSync(kept);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${kept} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n if (tool.entry === undefined) return path;\n } else {\n bytes = await download(asset.url);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${asset.url} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n writeWhole(kept, bytes, executable && tool.entry === undefined);\n if (tool.entry === undefined) return path;\n }\n const binary = tarEntry(bytes, tool.entry);\n if (!existsSync(path) || sha256(readFileSync(path)) !== sha256(binary)) writeWhole(path, binary, executable);\n return path;\n};\n","import { execFile } from \"node:child_process\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { ciliumMode } from \"./cilium.ts\";\nimport type { ClusterManifest } from \"./record.ts\";\nimport type { KindScenario } from \"./scenario.ts\";\nimport { NODE_IMAGES, cidrOverlap, kindConfig, nodeNames } from \"./scenario.ts\";\nimport { KIND, KUBECTL, ensureTool } from \"./tools.ts\";\n\nconst run = promisify(execFile);\n\n/** This function starts a program and gives its standard output. A failure gives its standard error in the message. */\nexport const command = async (\n program: string,\n args: readonly string[],\n options: { readonly env?: Readonly<Record<string, string>>; readonly input?: string; readonly timeout?: number } = {},\n): Promise<string> => {\n try {\n const child = run(program, [...args], {\n env: { ...process.env, ...options.env },\n maxBuffer: 256 << 20,\n timeout: options.timeout ?? 600_000,\n windowsHide: true,\n });\n if (options.input !== undefined) {\n child.child.stdin?.end(options.input);\n }\n const { stdout } = await child;\n return stdout.toString();\n } catch (error) {\n const failed = error as { stderr?: string; stdout?: string; message?: string };\n throw new Error(`${program} ${args.join(\" \")} failed: ${(failed.stderr ?? failed.message ?? \"\").toString().trim().slice(0, 2000)}`);\n }\n};\n\n/** The credentials of the API, as a container on the network of the cluster reaches it. */\nexport interface Credentials {\n readonly server: string;\n readonly ca: string;\n readonly cert: string;\n readonly key: string;\n}\n\n/** A cluster is one kind cluster that the lab made, with its own kubeconfig and Docker network. */\nexport interface Cluster {\n readonly scenario: KindScenario;\n readonly dir: string;\n readonly kubeconfig: string;\n readonly network: string;\n readonly nodes: readonly string[];\n kubectl(args: readonly string[], input?: string): Promise<string>;\n /** This method applies Kubernetes objects, as JSON on standard input. */\n apply(objects: readonly unknown[]): Promise<void>;\n /** This method starts a command in a node container. */\n exec(node: string, argv: readonly string[]): Promise<string>;\n /** This method copies a local Docker image into each node. */\n load(image: string): Promise<void>;\n credentials(): Promise<Credentials>;\n manifest(): Promise<ClusterManifest>;\n delete(): Promise<void>;\n}\n\n/** This function gives the subnets of the Docker networks, by their names. */\nconst dockerSubnets = async (): Promise<Array<[string, string]>> => {\n const ids = (await command(\"docker\", [\"network\", \"ls\", \"-q\"])).split(/\\s+/).filter((id) => id !== \"\");\n if (ids.length === 0) return [];\n const info = JSON.parse(await command(\"docker\", [\"network\", \"inspect\", ...ids])) as Array<{\n Name: string;\n IPAM?: { Config?: Array<{ Subnet?: string }> | null };\n }>;\n return info.flatMap((network) =>\n (network.IPAM?.Config ?? [])\n .map((config) => config.Subnet ?? \"\")\n .filter((subnet) => /^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$/.test(subnet))\n .map((subnet): [string, string] => [network.Name, subnet]),\n );\n};\n\n/**\n * This function makes a kind cluster from a scenario, in a folder that holds its config, its\n * kubeconfig and the cache of kubectl. The cluster gets a Docker network of its own. The function\n * refuses a pod or service subnet that overlaps a Docker network, because that breaks the traffic\n * between the nodes.\n */\nexport const createCluster = async (scenario: KindScenario, dir: string): Promise<Cluster> => {\n const kind = await ensureTool(KIND);\n const kubectlPath = await ensureTool(KUBECTL);\n mkdirSync(dir, { recursive: true });\n const kubeconfig = join(dir, \"kubeconfig\");\n const network = `systems-${scenario.name}`;\n const config = kindConfig(scenario);\n for (const [name, subnet] of await dockerSubnets()) {\n if (name === network) continue;\n for (const ours of [scenario.podSubnet, scenario.serviceSubnet])\n if (cidrOverlap(ours, subnet)) throw new Error(`the subnet ${ours} overlaps the Docker network ${name} (${subnet})`);\n }\n const file = join(dir, \"kind.json\");\n writeFileSync(file, `${JSON.stringify(config, null, 2)}\\n`);\n const env = { KIND_EXPERIMENTAL_DOCKER_NETWORK: network };\n const nodes = nodeNames(scenario);\n const kubectl = (args: readonly string[], input?: string) =>\n command(kubectlPath, [\"--kubeconfig\", kubeconfig, \"--cache-dir\", join(dir, \"kubectl-cache\"), ...args], input === undefined ? {} : { input });\n const cluster: Cluster = {\n scenario,\n dir,\n kubeconfig,\n network,\n nodes,\n kubectl,\n async apply(objects) {\n await kubectl([\"apply\", \"-f\", \"-\"], JSON.stringify({ apiVersion: \"v1\", kind: \"List\", items: objects }));\n },\n exec: (node, argv) => command(\"docker\", [\"exec\", node, ...argv]),\n async load(image) {\n await command(kind, [\"load\", \"docker-image\", \"--name\", scenario.name, image], { env, timeout: 900_000 });\n },\n async credentials() {\n const internal = join(dir, \"kubeconfig.internal\");\n writeFileSync(internal, await command(kind, [\"get\", \"kubeconfig\", \"--internal\", \"--name\", scenario.name], { env }));\n const view = JSON.parse(\n await command(kubectlPath, [\"--kubeconfig\", internal, \"config\", \"view\", \"--raw\", \"-o\", \"json\"]),\n ) as {\n clusters: Array<{ cluster: { server: string; \"certificate-authority-data\": string } }>;\n users: Array<{ user: { \"client-certificate-data\": string; \"client-key-data\": string } }>;\n };\n return {\n server: view.clusters[0]!.cluster.server,\n ca: view.clusters[0]!.cluster[\"certificate-authority-data\"],\n cert: view.users[0]!.user[\"client-certificate-data\"],\n key: view.users[0]!.user[\"client-key-data\"],\n };\n },\n async manifest() {\n const nodesJson = JSON.parse(await kubectl([\"get\", \"nodes\", \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string }; status: { nodeInfo: Record<string, string> } }>;\n };\n // the mode of kube-proxy from its configuration, or the version and the settings of Cilium\n const config = scenario.kubeProxy === null ? \"cilium-config\" : \"kube-proxy\";\n const data = (JSON.parse(await kubectl([\"-n\", \"kube-system\", \"get\", \"configmap\", config, \"-o\", \"json\"])) as { data: Record<string, string> }).data;\n const mode = scenario.kubeProxy === null ? ciliumMode(data) : (/^mode:\\s*(\\S*)/m.exec(data[\"config.conf\"] ?? \"\")?.[1] ?? \"\");\n const version = JSON.parse(await kubectl([\"version\", \"-o\", \"json\"])) as { serverVersion?: { gitVersion?: string } };\n return {\n kind: (await command(kind, [\"version\"])).trim(),\n nodeImage: NODE_IMAGES[scenario.kubernetes]!.image,\n server: version.serverVersion?.gitVersion ?? \"\",\n mode: mode.replaceAll('\"', \"\"),\n docker: (await command(\"docker\", [\"version\", \"--format\", \"{{.Server.Version}}\"])).trim(),\n nodes: nodesJson.items.map((item) => ({\n name: item.metadata.name,\n kubelet: item.status.nodeInfo[\"kubeletVersion\"] ?? \"\",\n kernel: item.status.nodeInfo[\"kernelVersion\"] ?? \"\",\n runtime: item.status.nodeInfo[\"containerRuntimeVersion\"] ?? \"\",\n os: item.status.nodeInfo[\"osImage\"] ?? \"\",\n })),\n };\n },\n async delete() {\n await command(kind, [\"delete\", \"cluster\", \"--name\", scenario.name, \"--kubeconfig\", kubeconfig], { env });\n await command(\"docker\", [\"network\", \"rm\", network]).catch(() => undefined);\n },\n };\n // With no CNI, no node is ready before the install of Cilium (`installCilium`), thus kind does not wait.\n const wait = scenario.cni === \"kindnet\" ? [\"--wait\", \"180s\"] : [];\n await command(kind, [\"create\", \"cluster\", \"--name\", scenario.name, \"--config\", file, \"--kubeconfig\", kubeconfig, ...wait], { env, timeout: 900_000 });\n return cluster;\n};\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { HELM, cacheFolder, download, ensureTool, sha256 } from \"./tools.ts\";\n\n/**\n * This module installs a Helm chart that is pinned by the sha256 of its file. The installs of Cilium\n * (`cilium.ts`) and of MetalLB (`metallb.ts`) use it. Helm keeps its own files in the folder of the\n * cluster, thus two clusters do not share a cache of Helm.\n */\n\n/** A pinned chart is a Helm chart: its name, its version, the URL of its file and the sha256 of that file. */\nexport interface PinnedChart {\n readonly name: string;\n readonly version: string;\n readonly url: string;\n readonly sha256: string;\n}\n\n/** This function gives the path of a chart in the cache, after a download when the cache does not have it. It refuses a file whose sha256 is not the pinned value. */\nexport const ensurePinnedChart = async (chart: PinnedChart, cache: string = cacheFolder()): Promise<string> => {\n const path = join(cache, \"charts\", `${chart.name}-${chart.version}.tgz`);\n const bytes = existsSync(path) ? readFileSync(path) : await download(chart.url);\n const found = sha256(bytes);\n if (found !== chart.sha256) throw new Error(`the chart ${chart.name} ${chart.version} has the sha256 ${found}, not the pinned ${chart.sha256}`);\n if (!existsSync(path)) {\n mkdirSync(join(cache, \"charts\"), { recursive: true });\n writeFileSync(path, bytes);\n }\n return path;\n};\n\n/**\n * This function installs a pinned chart in a cluster with the values `values`. It writes the values\n * to a file in the folder of the cluster. Helm waits for each workload of the chart, for up to 10 min.\n */\nexport const helmInstall = async (\n cluster: Pick<Cluster, \"dir\" | \"kubeconfig\">,\n release: string,\n chart: PinnedChart,\n namespace: string,\n values: Readonly<Record<string, unknown>>,\n options: { readonly createNamespace?: boolean } = {},\n): Promise<void> => {\n const helm = await ensureTool(HELM);\n const path = await ensurePinnedChart(chart);\n const file = join(cluster.dir, `${release}-values.json`);\n writeFileSync(file, `${JSON.stringify(values, null, 2)}\\n`);\n const home = join(cluster.dir, \"helm\");\n const env = { HELM_CACHE_HOME: join(home, \"cache\"), HELM_CONFIG_HOME: join(home, \"config\"), HELM_DATA_HOME: join(home, \"data\") };\n const create = options.createNamespace === true ? [\"--create-namespace\"] : [];\n await command(helm, [\"install\", release, path, \"-n\", namespace, ...create, \"--kubeconfig\", cluster.kubeconfig, \"-f\", file, \"--wait\", \"--timeout\", \"10m\"], { env, timeout: 900_000 });\n};\n","import type { Cluster } from \"./cluster.ts\";\nimport type { PinnedChart } from \"./helm.ts\";\nimport { ensurePinnedChart, helmInstall } from \"./helm.ts\";\nimport { cacheFolder } from \"./tools.ts\";\n\n/**\n * This module installs Cilium 1.20.4 in a kind cluster with Helm, from images pinned by digest.\n * Cilium is the CNI and replaces kube-proxy (the dataplane `cilium`). Each value of the install\n * that is not a default of the chart is in `ciliumValues`, with its reason.\n */\n\n/** This version is the release of Cilium of the lab, from 2026-10-23: the version of the chart and the tag of each image. */\nexport const CILIUM_VERSION = \"1.20.4\";\n\n/** This chart is the Helm chart of Cilium 1.20.4. The sha256 is of the file on `helm.cilium.io` (read on 2026-10-27). */\nexport const CILIUM_CHART: PinnedChart = {\n name: \"cilium\",\n version: CILIUM_VERSION,\n url: \"https://helm.cilium.io/cilium-1.20.4.tgz\",\n sha256: \"505abff8385811df1a949776fc42e9f10697989a332acc86d6e8933efcb855ed\",\n};\n\n/**\n * These are the images that the install needs, pinned by the digests that the chart of 1.20.4 names\n * (`values.yaml`). The agent, the operator and Envoy are the only images: the install has no\n * node init, no preflight and no Hubble relay. The lab pulls each one by its digest, tags it\n * (`localPin`), and loads it into each node. Thus no node pulls from a registry.\n */\nexport const CILIUM_IMAGES: { readonly agent: string; readonly operator: string; readonly envoy: string } = {\n agent: \"quay.io/cilium/cilium:v1.20.4@sha256:20fe92e4c86641233a450098c158d2d29085f3419931ab6bf681d2485dedf16e\",\n operator: \"quay.io/cilium/operator-generic:v1.20.4@sha256:7a7a3489dc66ad67e420d285c8a8d2e169b0957ad15e120f593d27081bf38d82\",\n envoy:\n \"quay.io/cilium/cilium-envoy:v1.39.5-1792733054-8c73b2816bc8239e27f1fd53b271884688137459@sha256:547ab45f6092f681d9f54f3be9fa8fb1e705c9c5dc2df388efe3a52aa6965629\",\n};\n\n/** These are the local tags of the images of Cilium, after the pull by digest. */\nexport interface CiliumImages {\n readonly agent: string;\n readonly operator: string;\n readonly envoy: string;\n}\n\n/**\n * This function gives the Helm values of the install in a cluster. The values that are not the\n * defaults of the chart:\n * - `kubeProxyReplacement: true`, and the address of the API server on the control plane. With no\n * kube-proxy, the agent cannot reach the API through its Service\n * (`Documentation/network/kubernetes/kubeproxy-free.rst`).\n * - `ipam.mode: kubernetes`: the agent takes the pod CIDR of each node from the node, as the kind\n * guide tells (`Documentation/installation/kind.rst`). Thus the pod subnet of the scenario holds.\n * - Each image is an `override`: the helper `cilium.image` of the chart then uses it as it is\n * (`templates/_helpers.tpl`). `pullPolicy: Never`: each image is on each node.\n * - The operator has one replica, not two. Its anti-affinity puts each replica on a different node,\n * and no cell examines a failover of the operator.\n *\n * Socket LB stays at its default. KPR sets it on (`pkg/kpr/kpr.go`). It selects a backend at\n * `connect()`. A connection keeps its backend, as with the conntrack NAT of kube-proxy. On the\n * deletion of a backend, the agent destroys only its UDP sockets, because\n * `LBSockTerminateAllProtos` is off by default (`pkg/loadbalancer/reconciler/termination.go`).\n */\nexport const ciliumValues = (clusterName: string, images: CiliumImages): Record<string, unknown> => ({\n kubeProxyReplacement: true,\n k8sServiceHost: `${clusterName}-control-plane`,\n k8sServicePort: 6443,\n ipam: { mode: \"kubernetes\" },\n image: { override: images.agent, pullPolicy: \"Never\" },\n operator: { replicas: 1, image: { override: images.operator, pullPolicy: \"Never\" } },\n envoy: { image: { override: images.envoy, pullPolicy: \"Never\" } },\n});\n\n/** These are the workloads in kube-system that the install waits for, then the DNS that waits for the CNI. */\nexport const CILIUM_WORKLOADS: readonly string[] = [\"daemonset/cilium\", \"deployment/cilium-operator\", \"daemonset/cilium-envoy\"];\n\n/** This function gives the path of the chart in the cache, after a download when the cache does not have it. It refuses a file whose sha256 is not the pinned value. */\nexport const ensureChart = (cache: string = cacheFolder()): Promise<string> => ensurePinnedChart(CILIUM_CHART, cache);\n\n/**\n * This function installs Cilium in a cluster with no CNI and no kube-proxy. Helm keeps its own\n * files in the folder of the cluster. The function waits for each workload, for each node to be\n * ready, and for CoreDNS. If the install fails, the error has the pods of kube-system and the last\n * lines of the log of the agent.\n */\nexport const installCilium = async (cluster: Cluster, images: CiliumImages): Promise<void> => {\n try {\n await helmInstall(cluster, \"cilium\", CILIUM_CHART, \"kube-system\", ciliumValues(cluster.scenario.name, images));\n for (const workload of CILIUM_WORKLOADS) await cluster.kubectl([\"-n\", \"kube-system\", \"rollout\", \"status\", workload, \"--timeout=300s\"]);\n await cluster.kubectl([\"wait\", \"--for=condition=Ready\", \"nodes\", \"--all\", \"--timeout=300s\"]);\n await cluster.kubectl([\"-n\", \"kube-system\", \"rollout\", \"status\", \"deployment/coredns\", \"--timeout=300s\"]);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", \"kube-system\", ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const agent = await state([\"logs\", \"daemonset/cilium\", \"-c\", \"cilium-agent\", \"--tail=80\"]);\n throw new Error(`the install of Cilium failed: ${String(error)}\\nthe pods of kube-system:\\n${pods}\\nthe log of the agent:\\n${agent}`);\n }\n};\n\n/** This is the setting of Cilium that a cluster manifest records: the version and the keys of `cilium-config` that the cells depend on. */\nexport const CILIUM_CONFIG_KEYS: readonly string[] = [\"kube-proxy-replacement\", \"bpf-lb-sock\", \"bpf-lb-algorithm\", \"routing-mode\", \"ipam\"];\n\n/** This function gives the mode of a cluster manifest of the dataplane `cilium`, from the data of the configmap `cilium-config`. */\nexport const ciliumMode = (data: Readonly<Record<string, string>>): string =>\n `cilium ${CILIUM_VERSION} ${CILIUM_CONFIG_KEYS.map((key) => `${key}=${data[key] ?? \"default\"}`).join(\" \")}`;\n","// The composition of the service of a kind cell. The app of the image and `lint()` compose the same\n// service from the same declaration: the image copies this file, and Node strips its types. Thus\n// this file imports only packages and types.\nimport type { CheckFn, Coupling, Health, HealthServer, ProbeAnswer, ServiceBuilder, TransportImpl } from \"@mark1russell7/service\";\nimport { service } from \"@mark1russell7/service\";\nimport type { RedisClient } from \"@mark1russell7/service/redis\";\nimport type { Declaration } from \"./cell.ts\";\n\n/** These are the parts of a running service. A composition for `lint()` gives none of them. */\nexport interface Parts {\n readonly redis?: RedisClient | undefined;\n readonly flag?: CheckFn | undefined;\n readonly http?: TransportImpl | undefined;\n readonly health?: HealthServer | undefined;\n}\n\n/** This name is the controller of the service, and its one route reads Redis when the service has Redis. */\nexport const CONTROLLER = \"work\";\nexport const ROUTE = \"GET /work\";\n/** This name is the check that a flag file fails: the seam of the liveness restart. */\nexport const FLAG_CHECK = \"self.flag\";\n/** This name is the watchdog of the event loop. */\nexport const WATCHDOG = \"self.event-loop\";\n\n/**\n * This function gives the health server of the shared gate (S6, candidate E). Its readiness probe\n * leaves out each check of `dependency`, a dependency that each replica shares. The controller still\n * reads each check, thus the service still refuses each request at once with a 503. The other\n * probes, and the readiness of a controller, answer as before.\n *\n * The probe passes when the service is started and not in the drain, and liveness does not fail\n * readiness through a coupling. Each readiness check of another dependency, or of no dependency,\n * must also be released.\n */\nexport const sharedGate = (server: HealthServer, dependency: string, couplings: readonly Coupling[]): HealthServer => {\n const gated = (health: Health): Health => ({\n verdict: () => health.verdict(),\n metrics: () => health.metrics(),\n subscribe: (listener) => health.subscribe(listener),\n forceFail: (name, on) => health.forceFail(name, on),\n drain: () => health.drain(),\n stop: () => health.stop(),\n probe(kind, controller): ProbeAnswer {\n const answer = health.probe(kind, controller);\n if (kind !== \"readiness\" || controller !== undefined) return answer;\n const verdict = health.verdict();\n const coupled = couplings.some((coupling) => coupling.from === \"liveness\" && coupling.to === \"readiness\") && !verdict.alive;\n const checks = Object.values(verdict.checks).every((check) => check.gates !== \"readiness\" || check.dependency === dependency || check.tripped !== true);\n const dependencies = Object.entries(verdict.dependencies).every(([name, up]) => name === dependency || up);\n const pass = verdict.started && !verdict.draining && !coupled && checks && dependencies;\n return { ...answer, status: pass ? 200 : 503, body: { ...answer.body, status: pass ? \"pass\" : \"fail\" } };\n },\n });\n return { listen: (health) => server.listen(gated(health)), close: () => server.close() };\n};\n\n/**\n * This function composes the service of a declaration: its policies, its couplings, its checks,\n * one server, one controller and each stage of the shutdown. A declaration with Redis needs the\n * client, because the client declares the dependency and its checks.\n */\nexport const compose = (name: string, declaration: Declaration, parts: Parts = {}): ServiceBuilder => {\n const svc = service(name)\n .readiness(declaration.readiness)\n .liveness(declaration.liveness)\n .startup(declaration.startup)\n .cooperativeWindow(declaration.cooperativeWindow)\n // the kubelet sends SIGTERM one time and SIGKILL at the end of the grace: a repeat signal has no effect\n .onRepeatSignal(\"ignore\");\n for (const coupling of declaration.couplings) svc.couple(coupling);\n if (declaration.redis !== null) {\n if (parts.redis === undefined) throw new Error(`${name} declares Redis, thus its composition needs the client`);\n parts.redis.attach(svc, { poll: declaration.poll, steps: { \"round-trip\": declaration.redis.gates } });\n }\n if (declaration.flag !== null)\n svc.check(\n {\n name: FLAG_CHECK,\n dependency: null,\n step: \"local-state\",\n ownConnection: true,\n gates: declaration.flag,\n poll: declaration.poll,\n },\n parts.flag,\n );\n if (declaration.watchdog !== null)\n svc.watchdog({\n name: WATCHDOG,\n tick: declaration.watchdog.tick,\n threshold: declaration.watchdog.threshold,\n gates: \"liveness\",\n poll: declaration.poll,\n });\n svc.server(\"http\", {}, parts.http);\n const controller = svc.controller(CONTROLLER);\n if (declaration.redis !== null) controller.requires(parts.redis!.name);\n controller.route(ROUTE, { idempotent: true });\n // the shared gate keeps the checks of Redis out of the readiness that the kubelet reads\n if (declaration.sharedGate === true && declaration.redis === null) throw new Error(`${name} has the shared gate, but it declares no Redis`);\n if (parts.health !== undefined)\n svc.healthServer(declaration.sharedGate === true ? sharedGate(parts.health, parts.redis!.name, declaration.couplings) : parts.health);\n for (const [stage, cap] of declaration.phases) svc.phase(stage, cap);\n return svc;\n};\n","// The hosts of one cluster in the Envoy of a sidecar, from the text of its admin endpoint\n// `/clusters`. The agent of the lab image reads it with Node, thus this module imports nothing.\n\n/**\n * These are the hosts of a cluster: the healthy ones (`b`, in sequence), and each other host with its\n * health flags (`u`). Each host is `ip:port`.\n */\nexport interface ClusterHosts {\n readonly b: readonly string[];\n readonly u: Readonly<Record<string, string>>;\n}\n\n/**\n * This function gives the name of the outbound cluster of a Service port in a sidecar of Istio:\n * `outbound|<port>||<service>.<namespace>.svc.cluster.local`.\n */\nexport const outboundCluster = (service: string, namespace: string, port: number): string => `outbound|${port}||${service}.${namespace}.svc.cluster.local`;\n\n/** This function gives the filter of `/clusters` that selects one cluster: a regular expression of RE2 with anchors. */\nexport const clusterFilter = (cluster: string): string => `^${cluster.replace(/[.|\\\\^$*+?()[\\]{}]/g, (c) => `\\\\${c}`)}$`;\n\n/**\n * This function gives the hosts of a cluster from the text of `/clusters`. Each host of the text has\n * a line `<cluster>::<ip:port>::health_flags::<flags>`. The flags are `healthy`, or the names of the\n * flags that the host has (`clusters_handler.cc` and `host_utility.cc` of Envoy v1.39.4).\n */\nexport const hostsOf = (text: string, cluster: string): ClusterHosts => {\n const healthy: string[] = [];\n const other: Record<string, string> = {};\n const prefix = `${cluster}::`;\n for (const line of text.split(\"\\n\")) {\n if (!line.startsWith(prefix)) continue;\n const parts = line.slice(prefix.length).split(\"::\");\n if (parts.length !== 3 || parts[1] !== \"health_flags\") continue;\n const [host, , flags] = parts as [string, string, string];\n if (flags.trim() === \"healthy\") healthy.push(host);\n else other[host] = flags.trim();\n }\n return { b: healthy.sort(), u: other };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Flap, KindCell } from \"./cell.ts\";\nimport { CLIENT, REDIS, SERVER } from \"./names.ts\";\nimport { HBONE_PORT } from \"./observe.ts\";\n\n/**\n * This module maps each trigger of a cell to the acts of the lab, as data. The runner finds each\n * target at the time of the run. A target is a pod, the PID of a container, the network namespace\n * of a pod, or a cgroup.\n */\n\n/**\n * A target is a process or a pod of the cell:\n * - `server` is the first replica of the service, in the sequence of its name. The injector acts in\n * its network namespace only, with each client pod as a peer: the partition of one replica.\n * - `redis` is the Redis pod.\n * - `kube-proxy` is kube-proxy on the first worker.\n * - `speaker` is the speaker of MetalLB on the owner of the VIP (SYS-57). Only a signal takes it.\n * - `broker` is the JVM of the Pulsar broker that owns the topic of the cell (`KindCell.pulsar`).\n * - `servers` is each replica of the service. Only the injector, a middlebox and a sysctl take it.\n * Each one acts in the network namespace of each replica, with each client pod as a peer. A REJECT\n * resets only the sender of the segment that it matches, thus the rule goes where the segments of\n * the clients arrive.\n * - `clients` is the process of each client pod. Only a signal, a sysctl and a drop of DNS take it.\n * They are the gap of `idle`, a sysctl of the namespace of each client pod, and `dns-blackhole`.\n */\nexport type Target = \"server\" | \"redis\" | \"kube-proxy\" | \"broker\" | \"servers\" | \"clients\" | \"speaker\";\n\n/** A step is one act of a fault, in the sequence of the plan. */\nexport type Step =\n | { readonly do: \"kubectl\"; readonly args: readonly string[]; readonly background?: boolean }\n /**\n * This step sends a signal to the target. For the target `clients`, it sends it to each client\n * process, the nodes at the same time. SIGUSR1 stops the calls of a gRPC client, and SIGUSR2 starts\n * them again. SIGHUP stops the calls and closes the channel, and the next SIGUSR2 opens a new channel.\n */\n | { readonly do: \"signal\"; readonly target: Target; readonly signal: \"SIGKILL\" | \"SIGSTOP\" | \"SIGCONT\" | \"SIGUSR1\" | \"SIGUSR2\" | \"SIGHUP\" }\n /** This step sends SIGSTOP, then SIGCONT `time` later, as the VM times it. The next step starts at once. */\n | { readonly do: \"pause\"; readonly target: Target; readonly time: Quantity }\n /**\n * This step starts the injector of the harness in the namespace of the target, with each replica of\n * the service as a peer. For the target `servers`, it starts it in the namespace of each replica,\n * with each client pod as a peer. The rules of all replicas commit at one instant. For the target\n * `server`, it starts it in the namespace of the target replica only, with each client pod as a peer.\n */\n | { readonly do: \"inject\"; readonly target: Target; readonly action: \"blackhole\" | \"clear\" | \"rst\" | \"clear-rst\" }\n | { readonly do: \"flag\"; readonly target: Target; readonly on: boolean }\n | { readonly do: \"cpu\"; readonly target: Target; readonly max: string }\n /** This step blocks the event loop of the target for `time`. */\n | { readonly do: \"stall\"; readonly target: Target; readonly time: Quantity }\n | { readonly do: \"wait\"; readonly time: Quantity }\n /** This step applies one object with `kubectl apply`. The runner fills the marks of its strings, for example `{namespace}`. */\n | { readonly do: \"apply\"; readonly object: Readonly<Record<string, unknown>> }\n /**\n * This step sets a sysctl of the network namespace of the target: each client pod (`clients`) or\n * each replica (`servers`). The injector of the harness enters each namespace in turn (`sysctlCommand`).\n */\n | { readonly do: \"sysctl\"; readonly target: Target; readonly key: string; readonly value: string }\n /**\n * This step puts a stateful middlebox in the network namespace of each replica (`servers`). Each\n * client pod is a peer, on the local port `port` (`middleboxCommands`). With `idle` null, the step\n * removes the middlebox and its chains.\n */\n | { readonly do: \"middlebox\"; readonly target: Target; readonly idle: Quantity | null; readonly port: number }\n /**\n * This step reads the workload certificates of the ztunnel pod of each node with istioctl. It adds a\n * line `certificates` with the validity of each leaf (`ztunnelCertificates`). It changes nothing.\n */\n | { readonly do: \"certificates\" }\n /**\n * This step loses the zone of the last worker (`lost`), or gives it back. The agent of the node\n * drops each packet on the link of the node, in its root network namespace. Then it freezes the\n * cgroup of the node. Thus each pod of the node and its kubelet stop at one instant. The step with `lost`\n * false thaws the cgroup, and then it removes the drops. It does nothing to a zone that is not lost.\n */\n | { readonly do: \"zone\"; readonly lost: boolean }\n /**\n * This step puts a qdisc netem on the link of the target, in its network namespace: each packet\n * that the target sends waits `delay`. A step on a link with a netem changes its delay, and the\n * packets in the queue keep their time. With `delay` null, the step removes the qdisc and each\n * packet in it. Only the target `redis` takes it.\n */\n | { readonly do: \"netem\"; readonly target: Target; readonly delay: Quantity | null }\n /**\n * This step writes the placement of the sessions of each client pod (`placementOf`): the backend of\n * each slot. `onTarget` of the sessions of all clients go to the target. The next SIGHUP of the\n * clients opens each slot again until its first answer comes from its backend.\n */\n | { readonly do: \"place\"; readonly onTarget: number }\n /**\n * This step drops each answer of a DNS server in the network namespace of each client pod\n * (`clients`), the nodes at the same time (`dnsCommands`). With `on` false, it removes the drops.\n */\n | { readonly do: \"dns\"; readonly target: Target; readonly on: boolean };\n\n/**\n * A fault plan is the steps of a run:\n * - `prepare` comes before the fault, and no reference comes from it: a sysctl, a middlebox, or a\n * stop of the calls. A plan without it has no such steps.\n * - `inject` starts the trigger. The stamp of its first act is the `fault` reference.\n * - `heal` ends the fault, `hold` after the fault. A trigger that ends by itself has no heal.\n * - `restore` brings the cell back to its steady state after the run.\n * - `flaps` are the faults after the first one. A plan without them has one fault.\n */\nexport interface FaultPlan {\n readonly prepare?: readonly Step[] | undefined;\n readonly inject: readonly Step[];\n readonly hold: Quantity | null;\n readonly heal: readonly Step[];\n readonly restore: readonly Step[];\n /** These flaps are the faults after the first one (`Trigger.flaps`). Each one does the inject and the heal again. */\n readonly flaps?: readonly Flap[] | undefined;\n}\n\nconst needs = (time: Quantity | undefined, what: string): Quantity => {\n if (time === undefined) throw new Error(`the trigger needs ${what}`);\n return time;\n};\n\n/** This function gives an amount in microseconds as a whole number, for `cpu.max`. */\nconst micros = (amount: Quantity): number => Math.round(valueIn(amount, \"us\"));\n\n/** This value marks the name of the target pod in the arguments of kubectl. The runner replaces it. */\nexport const POD = \"{pod}\";\n/** This value marks the node of the target pod in the arguments of kubectl. */\nexport const NODE = \"{node}\";\n/** This value marks the next version of the Deployment, for a rollout. */\nexport const VERSION = \"{version}\";\n/** This annotation of a pod is its deletion cost for its ReplicaSet: a lower cost goes first at a scale-in. */\nexport const DELETION_COST = \"controller.kubernetes.io/pod-deletion-cost\";\n/** This name is the name of the AuthorizationPolicy of the trigger `authorization-policy`. */\nexport const POLICY = \"lab-deny\";\n/** This service account is the account of the clients: their pods name no other account. */\nexport const CLIENT_ACCOUNT = \"default\";\n/** This selector gives the ztunnel pods in istio-system: the label `app` of the chart of ztunnel. */\nexport const ZTUNNEL_PODS = \"app=ztunnel\";\n\n/**\n * This function gives the AuthorizationPolicy of the trigger `authorization-policy`. It denies each\n * request to the server pods from the principal of the service account `account`. Istio gives a\n * pod of the mesh the principal `cluster.local/ns/<namespace>/sa/<account>` of its account. The\n * RBAC filter of the server sidecar reads the principal of the peer from its mTLS certificate.\n */\nexport const policyObject = (account: string): Readonly<Record<string, unknown>> => ({\n apiVersion: \"security.istio.io/v1\",\n kind: \"AuthorizationPolicy\",\n metadata: { name: POLICY, namespace: \"{namespace}\" },\n spec: {\n selector: { matchLabels: { app: SERVER } },\n action: \"DENY\",\n rules: [{ from: [{ source: { principals: [`cluster.local/ns/{namespace}/sa/${account}`] } }] }],\n },\n});\n\n/** These are the names of the sysctls that the steps of a plan set. Each one is a sysctl of a network namespace. */\nexport const SYSCTL = {\n retries: \"net.ipv4.tcp_retries2\",\n} as const;\n\n/**\n * These are the values of those sysctls in a new network namespace of Linux v6.17. A restore puts\n * them back. `tcp_retries2` is TCP_RETR2 (include/net/tcp.h, line 105, and net/ipv4/tcp_ipv4.c,\n * line 3604).\n */\nexport const NETNS_DEFAULTS = { retries: \"15\" } as const;\n\n/**\n * These are the names of the middlebox in the filter table of a replica. One chain takes the\n * segments from the peers, and one chain takes the segments to them. The table of xt_recent holds\n * the time of the last segment of each peer.\n */\nexport const MIDDLEBOX = { input: \"LAB-MBX-IN\", output: \"LAB-MBX-OUT\", table: \"LAB-MBX\" } as const;\n\n/** This time is between the stop of the calls and the fault of a quiet black hole. The last answers and their ACKs, at most 200 ms late, come in it. */\nexport const QUIET_LEAD: Quantity = { value: 1, unit: \"s\" };\n\n/** This time is between the close of the old channel of each client and the fault of a gap with `close`. The stream of the old channel ends in it. */\nexport const CLOSE_LEAD: Quantity = { value: 2, unit: \"s\" };\n\n/** This time is how long each client calls on its new channel at the fault of a gap with `close`, before it closes it. */\nexport const CHANNEL_USE: Quantity = { value: 5, unit: \"s\" };\n\n/**\n * This time is between the SIGHUP of `cannot-serve` and its fault. Each client opens new connections\n * at the signal, and the requests on its old connections end in this time (the timeout and 100 ms).\n */\nexport const SPREAD_LEAD: Quantity = { value: 3, unit: \"s\" };\n\nconst IPV4 = /^(25[0-5]|2[0-4]\\d|1?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}$/;\n\n/**\n * This function gives the command of the injector that sets one sysctl of its network namespace.\n * Only a sysctl of the namespace (`net.`) and a whole number reach it.\n */\nexport const sysctlCommand = (key: string, value: string): string[] => {\n if (!/^net\\.[a-z0-9_.]+$/.test(key)) throw new Error(`not a sysctl of a network namespace: ${key}`);\n if (!/^\\d+$/.test(value)) throw new Error(`not a whole number: ${value}`);\n return [\"sysctl\", \"-w\", `${key}=${value}`];\n};\n\n/** This port is the port of a DNS server: each answer of the cluster DNS comes from it. */\nexport const DNS_PORT = 53;\n\n/**\n * This function gives the commands of the injector that drop each answer of a DNS server in its\n * network namespace, on UDP and on TCP. With `on` false, they remove the drops. The rules go in\n * INPUT, thus each query still goes out, and CoreDNS answers it. Only the answer is lost. A send of\n * the resolver gets no error, as for a DNS server with no route back. The rules match the source port,\n * thus each DNS server of the namespace is in the fault.\n */\nexport const dnsCommands = (on: boolean): string[][] => {\n const op = on ? \"-I\" : \"-D\";\n return ([\"udp\", \"tcp\"] as const).map((protocol) => [\"iptables\", op, \"INPUT\", \"-p\", protocol, \"--sport\", String(DNS_PORT), \"-j\", \"DROP\"]);\n};\n\n/**\n * This function gives the commands of the injector that put a stateful middlebox in its network\n * namespace, with each peer, on the local port `port`. With `idle` null, they remove it.\n *\n * The middlebox forgets a flow of the port after `idle` with no segment. Then it drops each later\n * segment of it: the silent-drop form of the router of the harness. Conntrack stays as it is. The\n * local leg from ztunnel to the app has no PING, and istio-cni needs its conntrack entry. The\n * connmark of that entry sends each answer of the app back to ztunnel (cni/pkg/iptables/iptables.go\n * of Istio 1.31.3, lines 172–178, 278–284 and 336–347).\n *\n * The match recent keeps a table in each network namespace (xt_recent.c of Linux v6.17). Its key is\n * the address of the peer: the source of a segment from it, or the destination of a segment to it\n * (lines 251–257). The chain of each direction has these rules, in sequence:\n * - In the input chain only, a SYN sets the time of its peer and passes: a new flow.\n * - A segment of a peer with a time in the last `idle` seconds passes, and it sets the time again\n * (lines 298–321).\n * - A segment of a peer with an older time is dropped, and the time stays old. Thus the flow stays forgotten.\n * - A segment of a peer that the table does not hold sets its time and passes (lines 283–290). That\n * is the flow that was open before the set.\n *\n * Each rule that passes a segment goes back to INPUT or OUTPUT (RETURN), thus the other rules of the\n * namespace still act on it. The key is an address, not a flow. Thus a new SYN of a peer also refreshes an\n * old flow of the peer. A cell of the middlebox opens no new flow in its gap.\n */\nexport const middleboxCommands = (peers: readonly string[], idle: Quantity | null, port: number): string[][] => {\n if (peers.length === 0) throw new Error(\"a middlebox needs a peer\");\n for (const peer of peers) if (!IPV4.test(peer)) throw new Error(`not an address: ${peer}`);\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`not a port: ${port}`);\n const op = idle === null ? \"-D\" : \"-I\";\n // the option of a port follows the protocol that loads its match\n const jumps = peers.flatMap((peer) => [\n [\"iptables\", op, \"INPUT\", \"-s\", `${peer}/32`, \"-p\", \"tcp\", \"--dport\", String(port), \"-j\", MIDDLEBOX.input],\n [\"iptables\", op, \"OUTPUT\", \"-d\", `${peer}/32`, \"-p\", \"tcp\", \"--sport\", String(port), \"-j\", MIDDLEBOX.output],\n ]);\n const chains = [MIDDLEBOX.input, MIDDLEBOX.output];\n // the chains are empty first: a jump that a failed fault did not add does not keep the drops\n if (idle === null) return [...chains.map((chain) => [\"iptables\", \"-F\", chain]), ...jumps, ...chains.map((chain) => [\"iptables\", \"-X\", chain])];\n const time = valueIn(idle, \"s\");\n if (!Number.isInteger(time) || time < 1) throw new Error(`the idle timeout of the middlebox is a whole number of seconds, not ${time}`);\n const rules = (chain: string, side: \"--rsource\" | \"--rdest\"): string[][] => {\n const recent = [\"-m\", \"recent\", \"--name\", MIDDLEBOX.table, side];\n return [\n ...(chain === MIDDLEBOX.input ? [[\"iptables\", \"-A\", chain, \"-p\", \"tcp\", \"--syn\", ...recent, \"--set\", \"-j\", \"RETURN\"]] : []),\n [\"iptables\", \"-A\", chain, ...recent, \"--update\", \"--seconds\", String(time), \"-j\", \"RETURN\"],\n [\"iptables\", \"-A\", chain, ...recent, \"--rcheck\", \"-j\", \"DROP\"],\n [\"iptables\", \"-A\", chain, ...recent, \"--set\", \"-j\", \"RETURN\"],\n ];\n };\n return [...chains.map((chain) => [\"iptables\", \"-N\", chain]), ...rules(MIDDLEBOX.input, \"--rsource\"), ...rules(MIDDLEBOX.output, \"--rdest\"), ...jumps];\n};\n\n/**\n * This function gives the target of a reset or a black hole of a cell. In T07 and T18 it is Redis,\n * with the replicas as peers. In T15 the fault is on the mesh leg between the client pods and the\n * replicas. It is a rule in each replica, with the client pods as peers. A partition (`partition`)\n * is a rule in the target replica only, with the client pods as peers.\n */\nconst linkOf = (cell: KindCell): Target => (cell.trigger.partition === true ? \"server\" : cell.template === \"T15\" ? \"servers\" : \"redis\");\n\n/**\n * This function gives the placement of the sessions of the clients: the backend of each slot of each\n * client, by client. The clients come in the sequence of their names. The sessions on the target go\n * one to each client in turn: the first slot of each client, then the second slot of each client. Thus\n * no client has two more sessions on the target than another. The other slots go to the other\n * replicas in turn.\n */\nexport const placementOf = (clients: readonly string[], connections: number, onTarget: number, target: string, others: readonly string[]): Map<string, string[]> => {\n if (!Number.isInteger(connections) || connections < 1) throw new RangeError(`a placement needs one session of each client at the least, not ${connections}`);\n if (!Number.isInteger(onTarget) || onTarget < 0 || onTarget > clients.length * connections)\n throw new RangeError(`${onTarget} sessions on the target is not a count of the ${clients.length * connections} sessions of the clients`);\n if (others.length === 0 && onTarget < clients.length * connections) throw new RangeError(\"a session off the target needs another replica\");\n const sorted = [...clients].sort((a, b) => a.localeCompare(b));\n let other = 0;\n return new Map(\n sorted.map((client, at) => [\n client,\n Array.from({ length: connections }, (_, slot) => (slot * sorted.length + at < onTarget ? target : others[other++ % others.length]!)),\n ]),\n );\n};\n\n/**\n * This function puts the step `place` before the first SIGHUP of the clients, for a workload with a\n * placement (`Workload.onTarget`). A plan with no such SIGHUP cannot place the sessions.\n */\nconst placed = (cell: KindCell, plan: FaultPlan): FaultPlan => {\n const onTarget = cell.workload.onTarget;\n if (onTarget === undefined) return plan;\n if (cell.workload.connections === undefined) throw new Error(`${cell.name}: a placement needs the sessions of each client (connections)`);\n const prepare = plan.prepare ?? [];\n const at = prepare.findIndex((step) => step.do === \"signal\" && step.target === \"clients\" && step.signal === \"SIGHUP\");\n if (at === -1) throw new Error(`${cell.name}: the trigger ${cell.trigger.kind} has no SIGHUP of the clients before its fault, thus it cannot place the sessions`);\n return { ...plan, prepare: [...prepare.slice(0, at), { do: \"place\", onTarget }, ...prepare.slice(at)] };\n};\n\n/**\n * This function gives the fault plan of a cell. A trigger with flaps (`Trigger.flaps`) gives them to\n * its plan: the runner then does the inject and the heal again for each flap. A plan with no hold or\n * no heal cannot flap.\n */\nexport const planOf = (cell: KindCell): FaultPlan => {\n const plan = placed(cell, stepsOf(cell));\n const flaps = cell.trigger.flaps;\n if (flaps === undefined || flaps.length === 0) return plan;\n if (plan.hold === null || plan.heal.length === 0) throw new Error(`the trigger ${cell.trigger.kind} has no hold and heal, thus it cannot flap`);\n for (const flap of flaps) {\n if (valueIn(flap.rest, \"ms\") <= 0 || valueIn(flap.hold, \"ms\") <= 0) throw new Error(\"each flap has a rest and a hold above zero\");\n }\n return { ...plan, flaps };\n};\n\n/**\n * This function gives the time from the first fault of a trigger to its last heal. It is the hold,\n * then the rest and the hold of each flap. A trigger with no hold gives zero.\n */\nexport const faultSpan = (trigger: Pick<KindCell[\"trigger\"], \"hold\" | \"flaps\">): Quantity => {\n const hold = trigger.hold === undefined ? 0 : valueIn(trigger.hold, \"s\");\n const flaps = (trigger.flaps ?? []).reduce((sum, flap) => sum + valueIn(flap.rest, \"s\") + valueIn(flap.hold, \"s\"), 0);\n return { value: Math.round((hold + flaps) * 1e6) / 1e6, unit: \"s\" };\n};\n\n/** This device is the link of a pod: the end of its veth pair in its network namespace. */\nexport const POD_LINK = \"eth0\";\n\n/**\n * This time is how long the heal of a delay waits from the delay of zero to the removal of the\n * qdisc. Each packet in the queue leaves in it, because each one waits the delay at the most. A delay\n * of a cell must be shorter than it.\n */\nexport const NETEM_DRAIN: Quantity = { value: 1, unit: \"s\" };\n\n/** This function gives the steps of the fault plan of a cell, with no flaps. */\nconst stepsOf = (cell: KindCell): FaultPlan => {\n const ns = [\"-n\", \"{namespace}\"];\n const trigger = cell.trigger;\n const replicas = cell.pod.replicas;\n switch (trigger.kind) {\n case \"pod-deletion\":\n return { inject: [{ do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] }], hold: null, heal: [], restore: [] };\n case \"rolling-update\":\n return {\n inject: [{ do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] }],\n hold: null,\n heal: [],\n restore: [],\n };\n case \"node-drain\":\n return {\n inject: [\n {\n do: \"kubectl\",\n args: [\"drain\", NODE, \"--ignore-daemonsets\", \"--delete-emptydir-data\", \"--timeout=180s\", `--pod-selector=app=${SERVER}`],\n background: true,\n },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"uncordon\", NODE] }],\n };\n case \"scale-in\":\n return {\n inject: [\n // the ReplicaSet removes a pod of a lower deletion cost first. The rank by readiness comes before\n // it, and the pods of a node come after it (pkg/controller/controller_utils.go at v1.37.0, lines\n // 853–874). The gate PodDeletionCost is on from 1.22 (pkg/features/kube_features.go, line 1953).\n // Thus the clients of gRPC on the first replica see its stop\n ...(cell.stack === \"grpc\" ? [{ do: \"kubectl\" as const, args: [...ns, \"annotate\", \"pod\", POD, `${DELETION_COST}=-1`, \"--overwrite\"] }] : []),\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas - 1}`] },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas}`] }],\n };\n case \"crash\":\n // a cell with a broker stops the broker that owns its topic, not a replica of the service\n return { inject: [{ do: \"signal\", target: cell.pulsar === undefined ? \"server\" : \"broker\", signal: \"SIGKILL\" }], hold: null, heal: [], restore: [] };\n case \"liveness-restart\":\n // the container that the kubelet starts again has a new /tmp, thus the flag ends with it\n return { inject: [{ do: \"flag\", target: \"server\", on: true }], hold: null, heal: [], restore: [] };\n case \"freeze-process\":\n // a cell with a VIP freezes the speaker of the owner, with no other fault (SYS-57). Each flap is one more pause\n if (cell.vip !== undefined) {\n return {\n inject: [{ do: \"signal\", target: \"speaker\", signal: \"SIGSTOP\" }],\n hold: needs(trigger.hold, \"a hold, the time that the speaker is frozen\"),\n heal: [{ do: \"signal\", target: \"speaker\", signal: \"SIGCONT\" }],\n restore: [{ do: \"signal\", target: \"speaker\", signal: \"SIGCONT\" }],\n };\n }\n if (cell.template === \"T07\") {\n return {\n inject: [\n { do: \"pause\", target: \"kube-proxy\", time: needs(trigger.hold, \"a hold\") },\n { do: \"wait\", time: needs(trigger.lead, \"a lead\") },\n { do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] },\n ],\n hold: null,\n heal: [],\n restore: [],\n };\n }\n return {\n inject: [{ do: \"signal\", target: \"redis\", signal: \"SIGSTOP\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"signal\", target: \"redis\", signal: \"SIGCONT\" }],\n restore: [],\n };\n case \"rst\":\n return {\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"rst\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear-rst\" }],\n restore: [],\n };\n case \"blackhole\": {\n // a quiet black hole stops the calls before the fault, and starts them again after the clear.\n // The restore puts back the tcp_retries2 of each client pod, and starts the calls of a client\n // that a failed heal left stopped\n const quiet = trigger.quiet === true;\n const retries = trigger.retries;\n if (retries !== undefined && !(Number.isInteger(retries) && retries >= 1 && retries <= 255)) throw new Error(`tcp_retries2 is a count from 1 to 255, not ${retries}`);\n const start: Step[] = quiet ? [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" }] : [];\n // a partition gives each client new connections first, as cannot-serve does\n const spread: Step[] = trigger.partition === true ? [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }] : [];\n const prepare: Step[] = [\n ...spread,\n ...(retries === undefined ? [] : [{ do: \"sysctl\" as const, target: \"clients\" as const, key: SYSCTL.retries, value: String(retries) }]),\n ...(quiet ? [{ do: \"signal\" as const, target: \"clients\" as const, signal: \"SIGUSR1\" as const }, { do: \"wait\" as const, time: QUIET_LEAD }] : []),\n ];\n return {\n ...(prepare.length === 0 ? {} : { prepare }),\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"blackhole\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear\" }, ...start],\n restore: [...(retries === undefined ? [] : [{ do: \"sysctl\" as const, target: \"clients\" as const, key: SYSCTL.retries, value: NETNS_DEFAULTS.retries }]), ...start],\n };\n }\n case \"peer-gone-without-close\":\n // the rules drop the FIN of Redis, then Redis goes away with its namespace and its rules\n return {\n inject: [\n { do: \"inject\", target: \"redis\", action: \"blackhole\" },\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"app-stall\": {\n // in T07, each client opens new connections first, as a partition does. Thus they spread over the replicas in each run\n const spread: Step[] = cell.template === \"T07\" ? [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }] : [];\n return {\n ...(spread.length === 0 ? {} : { prepare: spread }),\n inject: [{ do: \"stall\", target: \"server\", time: needs(trigger.hold, \"a hold\") }],\n hold: null,\n heal: [],\n restore: [],\n };\n }\n case \"rollout\":\n // Redis is down for each pod, a new one too: the Service of Redis has no endpoint\n return {\n inject: [\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n { do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"cpu-throttle\": {\n const max = trigger.cpuMax;\n if (max === undefined) throw new Error(\"the trigger needs its cpu.max\");\n // the kernel reads cpu.max as the quota and the period in microseconds\n return {\n inject: [{ do: \"cpu\", target: \"server\", max: `${micros(max.quota)} ${micros(max.period)}` }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"cpu\", target: \"server\", max: `max ${micros(max.period)}` }],\n restore: [],\n };\n }\n case \"authorization-policy\":\n // the restore deletes a policy that a failed heal left\n return {\n inject: [{ do: \"apply\", object: policyObject(trigger.account ?? CLIENT_ACCOUNT) }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY] }],\n restore: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY, \"--ignore-not-found\"] }],\n };\n case \"idle\": {\n // the gap of the clients: the restore starts the calls of a client that a failed heal left stopped\n const hold = needs(trigger.hold, \"a hold, the time of the gap\");\n const start: Step = { do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" };\n if (trigger.close === true) {\n // each client closes its channel before the fault. At the fault it opens a new channel, calls\n // on it, and closes it. Thus the fault is the last use of the HBONE connection of the client\n const close: Step = { do: \"signal\", target: \"clients\", signal: \"SIGHUP\" };\n return { prepare: [close, { do: \"wait\", time: CLOSE_LEAD }], inject: [start, { do: \"wait\", time: CHANNEL_USE }, close], hold, heal: [start], restore: [start] };\n }\n const middlebox = trigger.middlebox;\n // the middlebox is in the namespace of each replica from before the gap to the end of the run\n return {\n ...(middlebox === undefined ? {} : { prepare: [{ do: \"middlebox\" as const, target: \"servers\" as const, idle: middlebox, port: HBONE_PORT }] }),\n inject: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR1\" }],\n hold,\n heal: [start],\n restore: [start, ...(middlebox === undefined ? [] : [{ do: \"middlebox\" as const, target: \"servers\" as const, idle: null, port: HBONE_PORT }])],\n };\n }\n case \"ztunnel-restart\":\n // the ztunnel pod of each node goes with its grace. The flow of each client crosses the ztunnel\n // of its own node, thus each client meets the restart. The restore waits for the DaemonSet\n return {\n inject: [{ do: \"kubectl\", args: [\"-n\", \"istio-system\", \"delete\", \"pod\", \"-l\", ZTUNNEL_PODS, \"--wait=false\"] }],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"-n\", \"istio-system\", \"rollout\", \"status\", \"daemonset/ztunnel\", \"--timeout=300s\"] }],\n };\n case \"certificate-expiry\":\n // the calls go on through the hold. The reads of the certificates are the evidence of their\n // lifetime, at the fault and at the heal. Thus a run shows each leaf that expired in the hold\n return {\n inject: [{ do: \"certificates\" }],\n hold: needs(trigger.hold, \"a hold, the time past the lifetime of the certificates\"),\n heal: [{ do: \"certificates\" }],\n restore: [],\n };\n case \"cannot-serve\":\n // each client opens its connections again before the fault, thus they spread over the replicas\n // in each run. The flag fails the route and the readiness of the target. The heal removes it\n return {\n prepare: [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }],\n inject: [{ do: \"flag\", target: \"server\", on: true }],\n hold: needs(trigger.hold, \"a hold, the time that the replica cannot serve\"),\n heal: [{ do: \"flag\", target: \"server\", on: false }],\n restore: [{ do: \"flag\", target: \"server\", on: false }],\n };\n case \"zone-loss\":\n // each client opens its connections again before the fault, as cannot-serve does. The node\n // lifecycle controller removes the replica of the lost zone. The restore gives back a zone\n // that a failed heal left lost\n return {\n prepare: [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }],\n inject: [{ do: \"zone\", lost: true }],\n hold: needs(trigger.hold, \"a hold, the time that the zone is lost\"),\n heal: [{ do: \"zone\", lost: false }],\n restore: [{ do: \"zone\", lost: false }],\n };\n case \"latency\": {\n // the heal sets the delay to zero first: a removal of the qdisc drops each reply in its queue.\n // The restore removes a qdisc that a failed heal left, and it does nothing to a link without one\n const delay = needs(trigger.delay, \"a delay, the time that each packet of Redis waits\");\n if (valueIn(delay, \"ms\") <= 0 || valueIn(delay, \"ms\") >= valueIn(NETEM_DRAIN, \"ms\")) throw new Error(`the delay of latency is above zero and below ${valueIn(NETEM_DRAIN, \"s\")} s`);\n const zero: Step = { do: \"netem\", target: \"redis\", delay: { value: 0, unit: \"s\" } };\n const back: readonly Step[] = [zero, { do: \"wait\", time: NETEM_DRAIN }, { do: \"netem\", target: \"redis\", delay: null }];\n return {\n inject: [{ do: \"netem\", target: \"redis\", delay }],\n hold: needs(trigger.hold, \"a hold, the time that Redis is slow\"),\n heal: back,\n restore: back,\n };\n }\n case \"dns-blackhole\":\n // the drops of each client pod, and their removal at the heal. A restore after a good heal\n // would find no rule to delete, thus the plan has none\n return {\n inject: [{ do: \"dns\", target: \"clients\", on: true }],\n hold: needs(trigger.hold, \"a hold, the time that no answer of DNS gets to a client\"),\n heal: [{ do: \"dns\", target: \"clients\", on: false }],\n restore: [],\n };\n }\n};\n\n/** 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. */\nexport const deletes = (cell: KindCell): boolean =>\n [\"pod-deletion\", \"rolling-update\", \"node-drain\", \"scale-in\"].includes(cell.trigger.kind) ||\n (cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\" && cell.vip === undefined);\n\n/** These are the names of the objects of a cell that a plan names. */\nexport const OBJECTS: { readonly server: typeof SERVER; readonly client: typeof CLIENT; readonly redis: typeof REDIS } = {\n server: SERVER,\n client: CLIENT,\n redis: REDIS,\n};\n","import { execFileSync } from \"node:child_process\";\nimport { copyFileSync, existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { imageOf, type BuildImage } from \"@mark1russell7/harness\";\n\n/** This folder is the root of the package, and its `image/` folder has the files of the image. */\nconst PACKAGE = fileURLToPath(new URL(\"../\", import.meta.url));\n/** This folder is the root of the repository, when the package is in the workspace of the repository. */\nconst REPO = fileURLToPath(new URL(\"../../../\", import.meta.url));\n/** These are the packages of this repository that the image holds, in the sequence of their dependencies. */\nexport const WORKSPACE_PACKAGES: readonly [\"quantity\", \"algebra\", \"service\"] = [\"quantity\", \"algebra\", \"service\"];\n/** This folder of a staged context holds the tarball of each package of the workspace. */\nconst PACKED = \"workspace\";\n/** These are the files of the image, by their name in the context and their source in the package. */\nconst FILES: ReadonlyArray<readonly [string, string]> = [\n [\"Dockerfile\", \"image/Dockerfile\"],\n [\"package.json\", \"image/package.json\"],\n [\"npm-lock.json\", \"image/npm-lock.json\"],\n [\"app.mjs\", \"image/app.mjs\"],\n [\"client.mjs\", \"image/client.mjs\"],\n [\"client-grpc.mjs\", \"image/client-grpc.mjs\"],\n [\"agent.mjs\", \"image/agent.mjs\"],\n [\"watch.mjs\", \"image/watch.mjs\"],\n [\"compose.ts\", \"src/compose.ts\"],\n [\"backends.ts\", \"src/backends.ts\"],\n [\"conntrack.ts\", \"src/conntrack.ts\"],\n [\"drops.ts\", \"src/drops.ts\"],\n [\"eds.ts\", \"src/eds.ts\"],\n [\"lds.ts\", \"src/lds.ts\"],\n [\"client-lines.ts\", \"src/client-lines.ts\"],\n [\"place.ts\", \"src/place.ts\"],\n];\n\nconst shell = process.platform === \"win32\";\n\n/** This function tells if the package is in the workspace of its repository. From npm, it is not. */\nconst inWorkspace = (): boolean =>\n existsSync(join(REPO, \"pnpm-workspace.yaml\")) && WORKSPACE_PACKAGES.every((name) => existsSync(join(REPO, \"packages\", name, \"package.json\")));\n\n/**\n * This function gives the folder of each installed package that the image holds. Node finds a\n * package from the folder of a package that depends on it. The service depends on the algebra,\n * thus the search starts with the service.\n */\nconst installedFolders = (): Map<string, string> => {\n const folders = new Map<string, string>();\n for (const name of [...WORKSPACE_PACKAGES].reverse()) {\n const found = [PACKAGE, ...folders.values()].flatMap((from) => {\n try {\n return [dirname(realpathSync(createRequire(join(from, \"package.json\")).resolve(`@mark1russell7/${name}/package.json`)))];\n } catch {\n return [];\n }\n })[0];\n if (found === undefined) throw new Error(`@mark1russell7/${name} is not installed with @mark1russell7/kind`);\n folders.set(name, found);\n }\n return folders;\n};\n\n/**\n * This function stages the build context of the lab image. In the workspace, it builds the packages\n * of the repository and packs each one as npm gets it. From npm, it packs the installed packages.\n * Then it copies the files of the image.\n */\nexport const stageLabImage = (): string => {\n const context = mkdtempSync(join(tmpdir(), \"systems-kind-lab-\"));\n const packed = join(context, PACKED);\n mkdirSync(packed);\n if (inWorkspace()) {\n const filters = WORKSPACE_PACKAGES.flatMap((name) => [\"--filter\", `@mark1russell7/${name}`]);\n execFileSync(\"pnpm\", [...filters, \"build\"], { cwd: REPO, shell, stdio: \"ignore\" });\n for (const name of WORKSPACE_PACKAGES)\n execFileSync(\"pnpm\", [\"pack\", \"--pack-destination\", packed], { cwd: join(REPO, \"packages\", name), shell, stdio: \"ignore\" });\n } else {\n for (const folder of installedFolders().values())\n execFileSync(\"npm\", [\"pack\", \"--pack-destination\", packed, \"--ignore-scripts\"], { cwd: folder, shell, stdio: \"ignore\" });\n }\n for (const [at, from] of FILES) copyFileSync(join(PACKAGE, from), join(context, at));\n return context;\n};\n\n/**\n * This function gives the source of the lab image from a staged context, as a `build` source of\n * the harness. The resolver of the harness plans it and builds it, as it does each other image.\n * Thus each `FROM` has a digest, and each manifest of packages has a lockfile.\n */\nexport const labSource = (context: string): BuildImage => ({\n kind: \"build\",\n name: \"systems-kind-lab\",\n context,\n files: [...FILES.map(([at]) => at), PACKED],\n});\n\nlet built: Promise<string> | null = null;\n\n/**\n * This function gives the tag of the lab image, one time for each process. It stages the context,\n * and the resolver of the harness builds it with a tag from its content (`imageOf`). The stage\n * builds and packs packages, thus a tag that passed stays. A stage or a build that fails leaves\n * the cache. Thus a later call stages and builds again after a fault of Docker or of the network.\n */\nexport const labImage = (): Promise<string> => {\n if (built !== null) return built;\n const building = (async () => {\n const context = stageLabImage();\n try {\n // the resolver copies the files into a context of its own before it builds\n return await imageOf(labSource(context));\n } finally {\n rmSync(context, { recursive: true, force: true });\n }\n })();\n // a failed build does not stay: the next caller tries again\n building.catch(() => {\n if (built === building) built = null;\n });\n built = building;\n return building;\n};\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { KindCell, MeshFlavor } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { outboundCluster } from \"./eds.ts\";\nimport { ZTUNNEL_PODS } from \"./faults.ts\";\nimport { SERVER } from \"./names.ts\";\nimport { ISTIOCTL, ensureTool } from \"./tools.ts\";\n\n/**\n * This module installs Istio 1.31.3 in a kind cluster with istioctl, from images pinned by digest.\n * The release notes of 1.31.2 and 1.31.3 change no default that a cell of T15 reads. Each value\n * of the install that is not a default of Istio is in `istioOperator`, with its reason.\n */\n\n/** This version is the release of Istio of the lab: the tag of istioctl and of each image. */\nexport const ISTIO_VERSION = \"1.31.3\";\n\n/**\n * These are the images of Istio, pinned by the digest of their index on Docker Hub (read on\n * 2026-10-24). `gcr.io/istio-release` has no final tag of 1.31. The lab pulls each one by its digest,\n * tags it with that digest (`localPin`), and loads it into each node. Thus no node pulls from a\n * registry. The flavor `sidecar` needs istiod and the proxy. The flavor `ambient` also needs\n * istio-cni and ztunnel (`MESH_IMAGES`).\n */\nexport const ISTIO_IMAGES: { readonly pilot: string; readonly proxy: string; readonly cni: string; readonly ztunnel: string } = {\n pilot: \"istio/pilot:1.31.3@sha256:6ad1e816c4d4767ba3f30a6286a96d121851ddb23d33e20736758acb5f3f4ea9\",\n proxy: \"istio/proxyv2:1.31.3@sha256:eb74eacea98d3fb1e239ea84016085155478586a554158676b9b67d960151084\",\n cni: \"istio/install-cni:1.31.3@sha256:e98782038634ef4fadaf78b53dd8fa4134fda3d65838ead5fc1edef245451c0f\",\n ztunnel: \"istio/ztunnel:1.31.3@sha256:2c27371b3139ecbf4f5a017354bd540889482441712e88cd8e654ead5a3eb5b2\",\n};\n\n/** These are the images that each flavor installs, by their names in `ISTIO_IMAGES`. */\nexport const MESH_IMAGES: Readonly<Record<MeshFlavor, ReadonlyArray<keyof typeof ISTIO_IMAGES>>> = {\n sidecar: [\"pilot\", \"proxy\"],\n ambient: [\"pilot\", \"proxy\", \"cni\", \"ztunnel\"],\n};\n\n/**\n * This format is the JSON access log of each sidecar. Each field is a command of the formatter of\n * Envoy v1.39.4. The fields give the status, the response flags and their details, and the gRPC\n * status as a number. They also give the tries of the router, the upstream host and cluster, and\n * the reasons of a failed connection. The watcher reads `code`, `flags`, `grpc` and `attempts`\n * (`image/watch.mjs`).\n */\nexport const ACCESS_LOG_FORMAT: Readonly<Record<string, string>> = {\n start: \"%START_TIME%\",\n code: \"%RESPONSE_CODE%\",\n flags: \"%RESPONSE_FLAGS%\",\n details: \"%RESPONSE_CODE_DETAILS%\",\n grpc: \"%GRPC_STATUS_NUMBER%\",\n attempts: \"%UPSTREAM_REQUEST_ATTEMPT_COUNT%\",\n host: \"%UPSTREAM_HOST%\",\n cluster: \"%UPSTREAM_CLUSTER%\",\n local: \"%DOWNSTREAM_LOCAL_ADDRESS%\",\n remote: \"%DOWNSTREAM_REMOTE_ADDRESS%\",\n duration: \"%DURATION%\",\n failure: \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\",\n termination: \"%CONNECTION_TERMINATION_DETAILS%\",\n};\n\n/** These are the local tags of the images, after the pull by digest. The flavor `ambient` needs `cni` and `ztunnel`. */\nexport interface MeshImages {\n readonly pilot: string;\n readonly proxy: string;\n readonly cni?: string | undefined;\n readonly ztunnel?: string | undefined;\n}\n\n/**\n * This function gives the IstioOperator of a flavor. The values that are not the defaults of Istio:\n * - The profile `minimal` is istiod only. The profile `default` also has an ingress gateway, which no cell uses.\n * - Each image is a full reference, thus the charts use it as it is (`contains \"/\"` in the\n * templates of istiod and of the injector). The init container of the injector has the image of the proxy too (`proxy_init`).\n * - `imagePullPolicy: Never`: each image is on each node. An image that the lab did not load fails the install at once.\n * - istiod requests 256 MiB, not 2 GiB, and has one replica with no autoscaler. Each node of kind\n * reports the whole runner, and no cell scales istiod.\n * - The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).\n *\n * The flavor `ambient` has the profile `ambient`: istiod, istio-cni and ztunnel, and no gateway\n * (manifests/profiles/ambient.yaml of Istio 1.31.3). Its two other images are full references too\n * (templates/daemonset.yaml of the chart of istio-cni, lines 93–96, and of ztunnel, lines 65–68).\n * The chart of istio-cni takes the pull policy of `global`. The template of ztunnel reads only its own\n * `imagePullPolicy`, thus the operator sets it there too.\n */\nexport const istioOperator = (flavor: MeshFlavor, images: MeshImages): Record<string, unknown> => {\n const ambient = flavor === \"ambient\";\n if (ambient && (images.cni === undefined || images.ztunnel === undefined)) throw new Error(\"the flavor ambient needs the images of istio-cni and ztunnel\");\n return {\n apiVersion: \"install.istio.io/v1alpha1\",\n kind: \"IstioOperator\",\n metadata: { name: \"lab\", namespace: \"istio-system\" },\n spec: {\n profile: ambient ? \"ambient\" : \"minimal\",\n hub: \"docker.io/istio\",\n tag: ISTIO_VERSION,\n meshConfig: {\n accessLogFile: \"/dev/stdout\",\n accessLogEncoding: \"JSON\",\n accessLogFormat: JSON.stringify(ACCESS_LOG_FORMAT),\n },\n values: {\n global: { imagePullPolicy: \"Never\", proxy: { image: images.proxy }, proxy_init: { image: images.proxy } },\n pilot: { image: images.pilot, autoscaleEnabled: false, replicaCount: 1, resources: { requests: { cpu: \"500m\", memory: \"256Mi\" } } },\n ...(ambient ? { cni: { image: images.cni }, ztunnel: { image: images.ztunnel, imagePullPolicy: \"Never\" } } : {}),\n },\n },\n };\n};\n\n/** These are the workloads of each flavor in istio-system that the install waits for. */\nexport const MESH_WORKLOADS: Readonly<Record<MeshFlavor, readonly string[]>> = {\n sidecar: [\"deployment/istiod\"],\n ambient: [\"deployment/istiod\", \"daemonset/istio-cni-node\", \"daemonset/ztunnel\"],\n};\n\n/**\n * This function installs the mesh of a flavor in a cluster with `istioctl install` and the operator\n * of the flavor. Then it waits for each workload of the flavor. The kubeconfig of the cluster goes to\n * istioctl. When the install fails, the error has the pods of istio-system and the last lines of the\n * log of istio-cni. Thus the log of the job shows the cause, for example a module that the kernel of\n * the runner does not have.\n */\nexport const installMesh = async (cluster: Cluster, flavor: MeshFlavor, images: MeshImages): Promise<void> => {\n const istioctl = await ensureTool(ISTIOCTL);\n const file = join(cluster.dir, `istio-${flavor}.json`);\n writeFileSync(file, `${JSON.stringify(istioOperator(flavor, images), null, 2)}\\n`);\n try {\n await command(istioctl, [\"install\", \"--kubeconfig\", cluster.kubeconfig, \"-y\", \"-f\", file, \"--readiness-timeout\", \"10m0s\"], { timeout: 900_000 });\n for (const workload of MESH_WORKLOADS[flavor]) await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", workload, \"--timeout=300s\"]);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", \"istio-system\", ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const cni = flavor === \"ambient\" ? `\\nthe log of istio-cni:\\n${await state([\"logs\", \"daemonset/istio-cni-node\", \"--tail=60\", \"--all-containers\"])}` : \"\";\n throw new Error(`the install of Istio ${flavor} failed: ${String(error)}\\nthe pods of istio-system:\\n${pods}${cni}`);\n }\n};\n\n/** This function gives the key of the mesh of a cell for its cluster: its flavor, or `none`. */\nexport const meshKey = (cell: Pick<KindCell, \"mesh\">): MeshFlavor | \"none\" => cell.mesh?.flavor ?? \"none\";\n\n/** This function gives the outbound cluster of the server of a cell in each client sidecar: the port 80 of its Service. */\nexport const serverCluster = (namespace: string): string => outboundCluster(SERVER, namespace, 80);\n\n/** This time is the interval of the EDS poll of each client sidecar, the same as the poll of the rules. */\nexport const EDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** This time is the interval of the LDS poll of each server sidecar, the same as the poll of EDS. */\nexport const LDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/**\n * This function tells if the watcher follows each ztunnel pod and its log for a cell: a cell in the\n * flavor `ambient`. A cell of sidecar pods in that flavor needs the lines of the ztunnel pods too.\n * They show the restart of ztunnel, which its pods do not see.\n */\nexport const followsZtunnel = (cell: Pick<KindCell, \"mesh\">): boolean => cell.mesh?.flavor === \"ambient\";\n\n/**\n * This function gives the arguments of kubectl that set the environment of ztunnel for a cell, or\n * remove it (`undo`). The variables go in the sequence of their names.\n */\nexport const ztunnelEnvArgs = (env: Readonly<Record<string, string>>, undo: boolean): string[] => [\n \"-n\",\n \"istio-system\",\n \"set\",\n \"env\",\n \"daemonset/ztunnel\",\n ...Object.keys(env)\n .sort()\n .map((key) => (undo ? `${key}-` : `${key}=${env[key]!}`)),\n];\n\n/**\n * This function sets the environment of ztunnel for a cell, or removes it (`undo`). Then it waits for\n * the rollout of the DaemonSet, and until no old ztunnel pod is left. An old pod drains for its\n * deadline after the rollout replaces it. Thus no run starts beside a ztunnel pod that is going.\n */\nexport const setZtunnelEnv = async (cluster: Cluster, env: Readonly<Record<string, string>>, undo: boolean): Promise<void> => {\n await cluster.kubectl(ztunnelEnvArgs(env, undo));\n await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", \"daemonset/ztunnel\", \"--timeout=300s\"]);\n const until = Date.now() + 120_000;\n for (;;) {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", \"istio-system\", \"get\", \"pods\", \"-l\", ZTUNNEL_PODS, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { deletionTimestamp?: string } }>;\n };\n if (listed.items.every((item) => item.metadata.deletionTimestamp === undefined)) return;\n if (Date.now() > until) throw new Error(\"an old ztunnel pod is still going 120 s after the rollout\");\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n};\n\n/** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */\nexport const pollsListeners = (cell: Pick<KindCell, \"mesh\" | \"trigger\">): boolean => cell.mesh?.mode === \"sidecar\" && cell.trigger.kind === \"authorization-policy\";\n\n/** This is the validity of one leaf certificate of ztunnel, as istioctl gives it: RFC 3339 times. */\nexport interface LeafCertificate {\n readonly pod: string;\n readonly identity: string;\n readonly state: string;\n readonly from: string | null;\n readonly to: string | null;\n}\n\n/** This function gives the arguments of istioctl that read the certificates of one ztunnel pod as JSON. */\nexport const certificateArgs = (pod: string, kubeconfig: string): string[] => [\n \"ztunnel-config\",\n \"certificate\",\n `${pod}.istio-system`,\n \"--kubeconfig\",\n kubeconfig,\n \"-o\",\n \"json\",\n];\n\n/**\n * This function gives the leaf of each identity in the JSON of `istioctl ztunnel-config certificate`.\n * The leaf is the first certificate of the chain (certificates.go of istioctl 1.31.3, lines 68–71).\n * An identity with no chain yet has no times.\n */\nexport const leafCertificates = (pod: string, json: string): LeafCertificate[] => {\n const dump = JSON.parse(json) as Array<{\n identity?: string;\n state?: string;\n certChain?: Array<{ validFrom?: string; expirationTime?: string }> | null;\n }>;\n return dump.map((each) => {\n const leaf = each.certChain?.[0];\n return { pod, identity: each.identity ?? \"\", state: each.state ?? \"\", from: leaf?.validFrom || null, to: leaf?.expirationTime || null };\n });\n};\n\n/**\n * This function reads the leaf certificates of each ztunnel pod that is not going, with istioctl.\n * The CLI reads the admin port of ztunnel through a port-forward. Each ztunnel binds that port to\n * localhost only (config.rs of ztunnel 1.31.3, lines 831–835).\n */\nexport const ztunnelCertificates = async (cluster: Cluster): Promise<LeafCertificate[]> => {\n const istioctl = await ensureTool(ISTIOCTL);\n const listed = JSON.parse(await cluster.kubectl([\"-n\", \"istio-system\", \"get\", \"pods\", \"-l\", ZTUNNEL_PODS, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string; deletionTimestamp?: string } }>;\n };\n const out: LeafCertificate[] = [];\n for (const item of listed.items.filter((each) => each.metadata.deletionTimestamp === undefined)) {\n const json = await command(istioctl, certificateArgs(item.metadata.name, cluster.kubeconfig), { timeout: 60_000 });\n out.push(...leafCertificates(item.metadata.name, json));\n }\n return out;\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { cidrRange } from \"./scenario.ts\";\n\n/**\n * This module installs kube-vip 1.3.1 in a kind cluster (SYS-51). The install gives a Service of type\n * LoadBalancer an address on the Docker network of the nodes: the VIP. In ARP mode, one node holds\n * the VIP on its link, and it sends a gratuitous ARP for it each `vip_arpRate`. A Lease elects that\n * node. Each value of the install is in `kubeVipObjects`, with its reason.\n *\n * The lab uses a VIP of a Service, not a VIP of the control plane. A VIP of the control plane needs\n * three control-plane nodes and TLS on port 6443. A VIP of a Service needs one DaemonSet on the\n * workers, and it serves the server of the cell.\n *\n * Each term of the election comes from the source of v1.3.1:\n * - With `svc_election` off, one election of the lease `plndr-svcs-lock` gives each VIP to one\n * node (pkg/manager/worker/common.go, `GlobalLeader`). The lease is in the namespace of the pod.\n * - The election is `leaderelection.RunOrDie` of client-go v0.37.1, with a Lease lock and\n * `ReleaseOnCancel` (pkg/election/election.go). The identity is the name of the node.\n * - A leader that loses its lease stops the process (`OnStoppedLeading`), and the kubelet starts it again.\n * - The ARP manager sends a gratuitous ARP for each VIP at each tick of one ticker of\n * `vip_arpRate`. A new VIP gets its first one at the next tick, not at once (pkg/arp/arp.go).\n */\n\n/** This version is the release of kube-vip of the lab, from 2026-10-19: the tag of its image. */\nexport const KUBE_VIP_VERSION = \"1.3.1\";\n\n/**\n * This image is kube-vip 1.3.1 for linux/amd64, pinned by the digest of its manifest. The digest of\n * the index of the tag is sha256:1962f9daf226503f7e6b107f595cb28b7917fd2d0113957075e19a3e5a6c1e33.\n * The manifest of one platform loads into kind with no index.\n */\nexport const KUBE_VIP_IMAGE = \"ghcr.io/kube-vip/kube-vip:v1.3.1@sha256:47353c7bde7fec21642cf621bc3fda2b999c051342912efda468999a399be667\";\n\n/** This is the name of kube-vip: of its ServiceAccount, its DaemonSet and its container. */\nexport const KUBE_VIP = \"kube-vip\";\n\n/** This is the Lease of the election of the VIPs of the Services (`svc_leasename`, cmd/kube-vip.go of v1.3.1). */\nexport const KUBE_VIP_LEASE: { readonly namespace: string; readonly name: string } = { namespace: \"kube-system\", name: \"plndr-svcs-lock\" };\n\n/**\n * These are the times of the election, as the install sets them. Each one is the default of the\n * flags of v1.3.1 (cmd/kube-vip.go, lines 91–94). They are a lease of 15 s, a renew deadline of 10 s\n * and a retry period of 2 s. The install sets each one in the environment, thus a new default cannot\n * change a cell.\n */\nexport const KUBE_VIP_ELECTION: { readonly lease: Quantity; readonly renew: Quantity; readonly retry: Quantity } = {\n lease: { value: 15, unit: \"s\" },\n renew: { value: 10, unit: \"s\" },\n retry: { value: 2, unit: \"s\" },\n};\n\n/** This time is the interval of the gratuitous ARPs, the default of the flag `arpRate` of v1.3.1 (cmd/kube-vip.go, line 61). */\nexport const KUBE_VIP_ARP_RATE: Quantity = { value: 3000, unit: \"ms\" };\n\n/** This label of a node selects the nodes of kube-vip: the DaemonSet has a pod only on a node with it. */\nexport const VIP_LABEL = \"lab.systems/vip\";\n\n/** This label of a pod of kube-vip selects them. */\nconst APP = { \"app.kubernetes.io/name\": KUBE_VIP };\n\nconst whole = (amount: Quantity): string => String(Math.round(valueIn(amount, \"s\")));\n\n/**\n * This function gives the objects of kube-vip in kube-system. They are its ServiceAccount, its\n * ClusterRole and binding, and its DaemonSet. The rules of the role are those of the manifest that\n * v1.3.1 generates (pkg/kubevip/config_generator.go, lines 95–134). The DaemonSet:\n * - is on the host network, with NET_ADMIN and NET_RAW. It adds the VIP to `eth0` and sends ARP.\n * - has a pod only on a node with `VIP_LABEL`. Thus the lab chooses the holder of the VIP.\n * - sets ARP mode, the Services, one election for all VIPs, and each time of the election.\n * - takes the image from the node (`imagePullPolicy: Never`).\n */\nexport const kubeVipObjects = (image: string): unknown[] => {\n const env: Record<string, string> = {\n vip_arp: \"true\",\n vip_interface: \"eth0\",\n port: \"6443\",\n cp_enable: \"false\",\n svc_enable: \"true\",\n svc_election: \"false\",\n svc_leasename: KUBE_VIP_LEASE.name,\n vip_leaderelection: \"true\",\n vip_leaseduration: whole(KUBE_VIP_ELECTION.lease),\n vip_renewdeadline: whole(KUBE_VIP_ELECTION.renew),\n vip_retryperiod: whole(KUBE_VIP_ELECTION.retry),\n vip_arpRate: String(Math.round(valueIn(KUBE_VIP_ARP_RATE, \"ms\"))),\n };\n const namespace = KUBE_VIP_LEASE.namespace;\n return [\n { apiVersion: \"v1\", kind: \"ServiceAccount\", metadata: { name: KUBE_VIP, namespace } },\n {\n apiVersion: \"rbac.authorization.k8s.io/v1\",\n kind: \"ClusterRole\",\n metadata: { name: \"system:kube-vip-role\" },\n rules: [\n { apiGroups: [\"\"], resources: [\"services/status\"], verbs: [\"update\"] },\n { apiGroups: [\"\"], resources: [\"services\", \"endpoints\"], verbs: [\"list\", \"get\", \"watch\", \"update\"] },\n { apiGroups: [\"\"], resources: [\"nodes\"], verbs: [\"list\", \"get\", \"watch\", \"update\", \"patch\"] },\n { apiGroups: [\"coordination.k8s.io\"], resources: [\"leases\"], verbs: [\"list\", \"get\", \"watch\", \"update\", \"create\"] },\n { apiGroups: [\"discovery.k8s.io\"], resources: [\"endpointslices\"], verbs: [\"list\", \"get\", \"watch\", \"update\"] },\n { apiGroups: [\"\"], resources: [\"pods\"], verbs: [\"list\"] },\n { apiGroups: [\"networking.k8s.io\"], resources: [\"servicecidrs\"], verbs: [\"list\", \"get\", \"watch\"] },\n ],\n },\n {\n apiVersion: \"rbac.authorization.k8s.io/v1\",\n kind: \"ClusterRoleBinding\",\n metadata: { name: \"system:kube-vip-binding\" },\n roleRef: { apiGroup: \"rbac.authorization.k8s.io\", kind: \"ClusterRole\", name: \"system:kube-vip-role\" },\n subjects: [{ kind: \"ServiceAccount\", name: KUBE_VIP, namespace }],\n },\n {\n apiVersion: \"apps/v1\",\n kind: \"DaemonSet\",\n metadata: { name: KUBE_VIP, namespace, labels: APP },\n spec: {\n selector: { matchLabels: APP },\n template: {\n metadata: { labels: APP },\n spec: {\n serviceAccountName: KUBE_VIP,\n hostNetwork: true,\n nodeSelector: { [VIP_LABEL]: \"1\" },\n containers: [\n {\n name: KUBE_VIP,\n image,\n imagePullPolicy: \"Never\",\n args: [\"manager\"],\n env: [\n ...Object.entries(env).map(([name, value]) => ({ name, value })),\n { name: \"vip_nodename\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n securityContext: { capabilities: { add: [\"NET_ADMIN\", \"NET_RAW\"] } },\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/** This is the Lease of the election, as the API gives it: its holder and its times. */\nexport interface VipLease {\n readonly holder: string | null;\n readonly acquireTime: string | null;\n readonly renewTime: string | null;\n readonly leaseDurationSeconds: number | null;\n readonly leaseTransitions: number | null;\n}\n\n/** This function gives the Lease of the election, or null when it is not there yet. */\nexport const vipLeaseOf = async (cluster: Pick<Cluster, \"kubectl\">): Promise<VipLease | null> => {\n const text = await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"get\", \"lease\", KUBE_VIP_LEASE.name, \"-o\", \"json\", \"--ignore-not-found\"]);\n if (text.trim() === \"\") return null;\n const spec = (JSON.parse(text) as { spec?: Record<string, unknown> }).spec ?? {};\n const text0 = (key: string): string | null => (typeof spec[key] === \"string\" && spec[key] !== \"\" ? (spec[key] as string) : null);\n const number0 = (key: string): number | null => (typeof spec[key] === \"number\" ? (spec[key] as number) : null);\n return {\n holder: text0(\"holderIdentity\"),\n acquireTime: text0(\"acquireTime\"),\n renewTime: text0(\"renewTime\"),\n leaseDurationSeconds: number0(\"leaseDurationSeconds\"),\n leaseTransitions: number0(\"leaseTransitions\"),\n };\n};\n\n/** This is the Docker network of a cluster: its IPv4 subnet, and the MAC address of each container on it, by name. */\nexport interface VipNetwork {\n readonly subnet: string;\n readonly macs: Readonly<Record<string, string>>;\n}\n\n/** This function reads the Docker network of a cluster: its IPv4 subnet, and the MAC address of each container. */\nexport const vipNetworkOf = async (network: string): Promise<VipNetwork> => {\n const [info] = JSON.parse(await command(\"docker\", [\"network\", \"inspect\", network])) as Array<{\n IPAM?: { Config?: Array<{ Subnet?: string }> | null };\n Containers?: Record<string, { Name?: string; MacAddress?: string }> | null;\n }>;\n const subnet = (info?.IPAM?.Config ?? []).map((config) => config.Subnet ?? \"\").find((each) => /^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$/.test(each));\n if (subnet === undefined) throw new Error(`the Docker network ${network} has no IPv4 subnet`);\n const macs: Record<string, string> = {};\n for (const container of Object.values(info?.Containers ?? {})) if (container.Name !== undefined && container.MacAddress !== undefined) macs[container.Name] = container.MacAddress.toLowerCase();\n return { subnet, macs };\n};\n\n/**\n * This function gives the VIP of a subnet: the sixth address from its end. Docker gives the address\n * of each container from the start of the subnet, thus no container takes it.\n */\nexport const vipOf = (subnet: string): string => {\n const [first, last] = cidrRange(subnet);\n const address = last - 5;\n if (address <= first + 1) throw new Error(`the subnet ${subnet} is too small for a VIP`);\n return [24, 16, 8, 0].map((shift) => Math.floor(address / 2 ** shift) % 256).join(\".\");\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\ninterface PodList {\n readonly items: ReadonlyArray<{\n readonly metadata: { readonly name: string; readonly deletionTimestamp?: string };\n readonly spec: { readonly nodeName?: string };\n readonly status?: { readonly containerStatuses?: ReadonlyArray<{ readonly ready: boolean; readonly restartCount?: number }> };\n }>;\n}\n\n/** This is a pod of kube-vip that is not deleted: its name, its node, if it is ready, and the restarts of its container. */\nexport interface KubeVipPod {\n readonly name: string;\n readonly node: string;\n readonly ready: boolean;\n readonly restarts: number;\n}\n\n/** This function gives the pods of kube-vip that are not deleted. */\nexport const kubeVipPods = async (cluster: Pick<Cluster, \"kubectl\">): Promise<KubeVipPod[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"get\", \"pods\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"-o\", \"json\"])) as PodList;\n return list.items\n .filter((pod) => pod.metadata.deletionTimestamp === undefined)\n .map((pod) => {\n const statuses = pod.status?.containerStatuses ?? [];\n return {\n name: pod.metadata.name,\n node: pod.spec.nodeName ?? \"\",\n ready: statuses.length > 0 && statuses.every((status) => status.ready),\n restarts: statuses.reduce((sum, status) => sum + (status.restartCount ?? 0), 0),\n };\n });\n};\n\n/** This function gives the nodes with a ready pod of kube-vip that is not deleted. */\nexport const kubeVipReady = async (cluster: Pick<Cluster, \"kubectl\">): Promise<string[]> =>\n (await kubeVipPods(cluster))\n .filter((pod) => pod.ready && pod.node !== \"\")\n .map((pod) => pod.node)\n .sort();\n\n/** This function waits until a condition gives null, and gives null, or the last reason after `within` ms. */\nconst until = async (within: number, check: () => Promise<string | null>): Promise<string | null> => {\n const end = Date.now() + within;\n let why = await check();\n while (why !== null && Date.now() < end) {\n await sleep(500);\n why = await check();\n }\n return why;\n};\n\nconst label = (cluster: Pick<Cluster, \"kubectl\">, node: string, on: boolean): Promise<string> =>\n cluster.kubectl([\"label\", \"node\", node, on ? `${VIP_LABEL}=1` : `${VIP_LABEL}-`, ...(on ? [\"--overwrite\"] : [])]);\n\n/**\n * This function makes `holder` the holder of the VIP, with a ready pod of kube-vip on each node of\n * `nodes`. It gives null, or why it could not in `within` ms. If another node holds the lease, the\n * function takes the label from each other node. The DaemonSet then deletes their pods. The leader\n * gives back its lease at its stop (`ReleaseOnCancel`). The function gives each label back after\n * `holder` takes the lease.\n *\n * A leader that lost its lease restarted its container. The kubelet gives a container a longer\n * back-off at each restart, thus the function first deletes each pod of kube-vip with a restart.\n * The DaemonSet then makes a new pod with no back-off.\n */\nexport const holdVip = async (cluster: Pick<Cluster, \"kubectl\">, holder: string, nodes: readonly string[], within = 180_000): Promise<string | null> => {\n const others = nodes.filter((node) => node !== holder);\n for (const pod of await kubeVipPods(cluster))\n if (pod.restarts > 0) await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"delete\", \"pod\", pod.name, \"--wait=true\", \"--timeout=120s\"]);\n await label(cluster, holder, true);\n const own = await until(within, async () => ((await kubeVipReady(cluster)).includes(holder) ? null : `kube-vip on ${holder} is not ready`));\n if (own !== null) return own;\n const held = async (): Promise<string | null> => {\n const lease = await vipLeaseOf(cluster);\n return lease?.holder === holder ? null : `the lease ${KUBE_VIP_LEASE.name} is held by ${lease?.holder ?? \"no node\"}, not ${holder}`;\n };\n if ((await held()) !== null) {\n for (const node of others) await label(cluster, node, false);\n const moved = await until(within, held);\n if (moved !== null) return moved;\n }\n for (const node of others) await label(cluster, node, true);\n return until(within, async () => {\n const ready = await kubeVipReady(cluster);\n const missing = nodes.filter((node) => !ready.includes(node));\n return missing.length === 0 ? null : `kube-vip is not ready on ${missing.join(\", \")}`;\n });\n};\n\n/**\n * This function installs kube-vip in a cluster, with its image on each node (`cluster.load`), and\n * makes `holder` the holder of the VIP. A failed install gives the pods of kube-vip and the tail of\n * their logs.\n */\nexport const installKubeVip = async (cluster: Cluster, image: string, holder: string, nodes: readonly string[]): Promise<void> => {\n try {\n await label(cluster, holder, true);\n await cluster.apply(kubeVipObjects(image));\n const why = await holdVip(cluster, holder, nodes);\n if (why !== null) throw new Error(why);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, ...args]).catch((failure: unknown) => String(failure))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"-o\", \"wide\"]);\n const logs = await state([\"logs\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"--tail=40\", \"--prefix\"]);\n throw new Error(`the install of kube-vip failed: ${String(error)}\\nthe pods of kube-vip:\\n${pods}\\nthe logs:\\n${logs}`);\n }\n};\n\n/**\n * This function waits until the Service of a VIP has the VIP in its status: kube-vip took it. It\n * gives null, or why not in `within` ms.\n */\nexport const vipServed = (cluster: Pick<Cluster, \"kubectl\">, namespace: string, service: string, vip: string, within = 120_000): Promise<string | null> =>\n until(within, async () => {\n const ingress = (await cluster.kubectl([\"-n\", namespace, \"get\", \"service\", service, \"-o\", \"jsonpath={.status.loadBalancer.ingress[*].ip}\"])).trim();\n return ingress.split(/\\s+/).includes(vip) ? null : `the Service ${service} has the ingress ${ingress || \"none\"}, not ${vip}`;\n });\n","import { createHash } from \"node:crypto\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { MemberlistSettings } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport type { PinnedChart } from \"./helm.ts\";\nimport { helmInstall } from \"./helm.ts\";\nimport { VIP_LABEL } from \"./kubevip.ts\";\nimport { cidrRange } from \"./scenario.ts\";\n\n/**\n * This module installs MetalLB 0.16.1 in L2 mode in a kind cluster (SYS-53). The controller of\n * MetalLB gives a Service of type LoadBalancer an address from a pool: the VIP. A speaker on each\n * worker answers ARP for the VIP if its node owns the VIP. The VIP is on no interface of a node.\n * Thus a frozen speaker answers nothing.\n *\n * Each term of the owner comes from the source of the tag v0.16.1 (commit 791dfcc6):\n * - The owner of a VIP is the first node in the order of sha256(node + \"#\" + VIP), of the nodes with\n * a usable speaker (speaker/layer2_controller.go, lines 83–129). A usable speaker is a member of\n * memberlist that is not dead (internal/speakerlist/speakerlist.go, lines 303–320).\n * - The speakers start memberlist from `DefaultLANConfig` (speakerlist.go, line 72). A file\n * /etc/metallb/memberlist/config.json replaces that config (lines 77–84). Without settings of\n * memberlist, the install makes no such file. Then each value is the default of memberlist v0.5.1.\n * - Each event of memberlist (a join, a leave or an update) calls `ForceSync` (speakerlist.go, lines\n * 339–349). `ForceSync` reconciles each Service at once, with no delay (internal/k8s/k8s.go, lines\n * 181–184, and internal/k8s/controllers/service_controller_reload.go, lines 62–118).\n * - The owner sends a gratuitous ARP at each reconcile. It sends one at once, then one each 1100 ms\n * for 5 s (internal/layer2/announcer.go, lines 157–197 and 258–272).\n */\n\n/** This version is the release of MetalLB of the lab: the tag v0.16.1, the commit 791dfcc63ced7a6b3e869131eae75e645150fff0. */\nexport const METALLB_VERSION = \"0.16.1\";\n\n/** This chart is the Helm chart of MetalLB 0.16.1. The sha256 is of the file of the release, and it is the digest in the index of the charts. */\nexport const METALLB_CHART: PinnedChart = {\n name: \"metallb\",\n version: METALLB_VERSION,\n url: \"https://github.com/metallb/metallb/releases/download/metallb-chart-0.16.1/metallb-0.16.1.tgz\",\n sha256: \"fb06bb584fcb7856f15733b2a6a2aff5b61b5c350687e341c163ae24a5938adc\",\n};\n\n/**\n * These are the images of MetalLB 0.16.1 for linux/amd64, pinned by the digest of the manifest of the\n * platform. The lab pulls each one by its digest, tags it (`localPin`), and loads it into each node.\n */\nexport const METALLB_IMAGES: { readonly speaker: string; readonly controller: string } = {\n speaker: \"quay.io/metallb/speaker:v0.16.1@sha256:37a98a9d1cd970051c5dededb6f922c1e6c3b90fdd0fc1350d1686e67675af0e\",\n controller: \"quay.io/metallb/controller:v0.16.1@sha256:5a3e101335f5ea2cfb0ddf51acdfa9538251e0623ffcd6cbfc1b274b7898790c\",\n};\n\n/** These are the local tags of the images of MetalLB, after the pull by digest. */\nexport interface MetallbImages {\n readonly speaker: string;\n readonly controller: string;\n}\n\n/** This namespace holds MetalLB: its controller, its speakers and its objects. */\nexport const METALLB_NAMESPACE = \"metallb-system\";\n\n/** This annotation of a Service gives the controller of MetalLB the address of its VIP. */\nexport const METALLB_ANNOTATION = \"metallb.io/loadBalancerIPs\";\n\n/** This name is the name of the pool of the VIP and of its L2Advertisement. */\nexport const METALLB_POOL = \"lab-vip\";\n\n/** This annotation of the Service makes each speaker reconcile it. The owner then sends its gratuitous ARPs. */\nexport const ANNOUNCE_ANNOTATION = \"lab.systems/announce\";\n\n/**\n * These are the terms of memberlist v0.5.1 that the speakers use: the defaults of `DefaultLANConfig`\n * (config.go, lines 302–341).\n * - `probeInterval` is the interval of the probes (line 318). The awareness of a healthy member does not scale it.\n * - `probeTimeout` is the wait for the ack of a direct ping (line 317).\n * - `suspicionMult` and `suspicionMaxTimeoutMult` give the time from suspect to dead (lines 314–315).\n * - `pushPullInterval` is the interval of the full exchange of state (line 316).\n * - `gossipInterval` is the interval of the gossip (line 323).\n */\nexport const MEMBERLIST: {\n readonly probeInterval: Quantity;\n readonly probeTimeout: Quantity;\n readonly suspicionMult: number;\n readonly suspicionMaxTimeoutMult: number;\n readonly pushPullInterval: Quantity;\n readonly gossipInterval: Quantity;\n} = {\n probeInterval: { value: 1, unit: \"s\" },\n probeTimeout: { value: 500, unit: \"ms\" },\n suspicionMult: 4,\n suspicionMaxTimeoutMult: 6,\n pushPullInterval: { value: 30, unit: \"s\" },\n gossipInterval: { value: 200, unit: \"ms\" },\n};\n\n/**\n * These are the values of the config file of memberlist, as `DefaultLANConfig` of v0.5.1 gives them\n * (config.go, lines 302–341). The speaker needs each field of the file, and it reads a time as a\n * duration of Go (speakerlist.go, lines 355–432). The chart mounts the ConfigMap\n * `metallb-memberlist-config` at /etc/metallb/memberlist, if the ConfigMap exists\n * (charts/metallb/templates/speaker.yaml, lines 195–198 and 391–392). The key is `config.json`.\n */\nexport const MEMBERLIST_FILE: Readonly<Record<string, string | number | boolean>> = {\n TCPTimeout: \"10s\",\n IndirectChecks: 3,\n RetransmitMult: 4,\n SuspicionMult: 4,\n SuspicionMaxTimeoutMult: 6,\n PushPullInterval: \"30s\",\n ProbeTimeout: \"500ms\",\n ProbeInterval: \"1s\",\n DisableTcpPings: false,\n AwarenessMaxMultiplier: 8,\n GossipNodes: 3,\n GossipInterval: \"200ms\",\n GossipToTheDeadTime: \"30s\",\n GossipVerifyIncoming: true,\n GossipVerifyOutgoing: true,\n EnableCompression: true,\n HandoffQueueDepth: 1024,\n UDPBufferSize: 1400,\n QueueCheckInterval: \"30s\",\n};\n\n/** This name is the ConfigMap of the config file of memberlist. */\nconst MEMBERLIST_CONFIG = \"metallb-memberlist-config\";\n\n/** This function gives a time as a duration of Go in whole milliseconds, for example \"500ms\". */\nconst goDuration = (time: Quantity): string => {\n const ms = valueIn(time, \"ms\");\n if (!Number.isInteger(ms) || ms <= 0) throw new RangeError(`a time of memberlist is a whole count of milliseconds above zero, not ${ms} ms`);\n return `${ms}ms`;\n};\n\n/** This function gives the config file of memberlist with `settings`. Each other value is the default of `MEMBERLIST_FILE`. */\nexport const memberlistFileOf = (settings: MemberlistSettings): Record<string, string | number | boolean> => {\n if (!Number.isInteger(settings.suspicionMult) || settings.suspicionMult < 1) throw new RangeError(`SuspicionMult is a whole number of 1 or more, not ${settings.suspicionMult}`);\n if (valueIn(settings.probeTimeout, \"ms\") >= valueIn(settings.probeInterval, \"ms\")) throw new RangeError(\"the probe timeout of memberlist is less than its probe interval\");\n return { ...MEMBERLIST_FILE, ProbeInterval: goDuration(settings.probeInterval), ProbeTimeout: goDuration(settings.probeTimeout), SuspicionMult: settings.suspicionMult };\n};\n\n/** This function gives the terms of memberlist with `settings`, or the defaults (`MEMBERLIST`) without them. */\nexport const memberlistTermsOf = (settings: MemberlistSettings | undefined): typeof MEMBERLIST =>\n settings === undefined ? MEMBERLIST : { ...MEMBERLIST, probeInterval: settings.probeInterval, probeTimeout: settings.probeTimeout, suspicionMult: settings.suspicionMult };\n\n/** These are the gratuitous ARPs of the owner at each reconcile: one at once, then one each `every` until `for` (announcer.go, lines 172–182). */\nexport const METALLB_SPAM: { readonly every: Quantity; readonly for: Quantity } = {\n every: { value: 1100, unit: \"ms\" },\n for: { value: 5, unit: \"s\" },\n};\n\n/**\n * This function gives the time from suspect to dead in memberlist v0.5.1, for a cluster of `members`\n * members with no peer that can confirm. The terms:\n * - `suspicionTimeout` is SuspicionMult × max(1, log10(n)) × ProbeInterval (util.go, lines 71–76).\n * - `suspectNode` sets k = SuspicionMult − 2 confirmations, and k = 0 if n − 2 < k (state.go, lines 1216–1224).\n * - With k = 0, the timer fires at the minimum, not at the maximum (suspicion.go, lines 70–76).\n * With confirmations to wait for, the function gives the maximum: SuspicionMaxTimeoutMult times the minimum.\n */\nexport const suspicionOf = (members: number, terms: Pick<typeof MEMBERLIST, \"probeInterval\" | \"suspicionMult\" | \"suspicionMaxTimeoutMult\"> = MEMBERLIST): Quantity => {\n const scale = Math.max(1, Math.log10(Math.max(1, members)));\n const min = Math.floor(scale * 1000) * valueIn(terms.probeInterval, \"ms\") * terms.suspicionMult / 1000;\n const k = members - 2 < terms.suspicionMult - 2 ? 0 : terms.suspicionMult - 2;\n return { value: k < 1 ? min : min * terms.suspicionMaxTimeoutMult, unit: \"ms\" };\n};\n\n/**\n * This function gives the Helm values of the install. The values that are not the defaults of the chart:\n * - No FRR and no frr-k8s: the lab uses L2 mode only, thus it needs no BGP.\n * - A speaker only on a node with `VIP_LABEL`, the workers. Thus memberlist has the workers as its\n * members, and the control plane does not answer ARP.\n * - The controller on the control plane. A zone loss of a worker then does not take it away.\n * - Each image from the node (`pullPolicy: Never`).\n */\nexport const metallbValues = (images: MetallbImages): Record<string, unknown> => {\n const image = (local: string) => {\n const at = local.lastIndexOf(\":\");\n if (at <= 0) throw new Error(`the image ${local} has no tag`);\n return { repository: local.slice(0, at), tag: local.slice(at + 1), pullPolicy: \"Never\" };\n };\n return {\n frrk8s: { enabled: false },\n controller: {\n image: image(images.controller),\n nodeSelector: { \"node-role.kubernetes.io/control-plane\": \"\" },\n tolerations: [{ key: \"node-role.kubernetes.io/control-plane\", operator: \"Exists\", effect: \"NoSchedule\" }],\n },\n speaker: {\n image: image(images.speaker),\n frr: { enabled: false },\n tolerateMaster: false,\n nodeSelector: { [VIP_LABEL]: \"1\" },\n memberlist: { enabled: true, mlBindPort: 7946 },\n },\n };\n};\n\n/** This function gives the pool of the VIP and its L2Advertisement. The pool has only the VIP. */\nexport const metallbObjects = (vip: string): unknown[] => [\n {\n apiVersion: \"metallb.io/v1beta1\",\n kind: \"IPAddressPool\",\n metadata: { name: METALLB_POOL, namespace: METALLB_NAMESPACE },\n spec: { addresses: [`${vip}/32`] },\n },\n {\n apiVersion: \"metallb.io/v1beta1\",\n kind: \"L2Advertisement\",\n metadata: { name: METALLB_POOL, namespace: METALLB_NAMESPACE },\n spec: { ipAddressPools: [METALLB_POOL] },\n },\n];\n\n/** This function gives the nodes in the order of the election of the owner of `vip`: by sha256(node + \"#\" + vip), as bytes (layer2_controller.go, lines 115–120). */\nexport const ownerOrder = (nodes: readonly string[], vip: string): string[] => {\n const hash = (node: string) => createHash(\"sha256\").update(`${node}#${vip}`).digest();\n return [...nodes].sort((a, b) => Buffer.compare(hash(a), hash(b)));\n};\n\nconst dotted = (address: number): string => [24, 16, 8, 0].map((shift) => Math.floor(address / 2 ** shift) % 256).join(\".\");\n\n/**\n * This function gives the VIP of a subnet for MetalLB. It looks down from the sixth address from the\n * end, and it gives the first address that `owner` owns, of the nodes `nodes`. Docker gives the address of each container\n * from the start of the subnet, thus no container takes it.\n */\nexport const metallbVipOf = (subnet: string, owner: string, nodes: readonly string[]): string => {\n if (!nodes.includes(owner)) throw new Error(`the owner ${owner} is not one of the nodes ${nodes.join(\", \")}`);\n const [first, last] = cidrRange(subnet);\n for (let address = last - 5; address > first + 1 && address > last - 260; address--) {\n const vip = dotted(address);\n if (ownerOrder(nodes, vip)[0] === owner) return vip;\n }\n throw new Error(`the subnet ${subnet} has no VIP that ${owner} owns`);\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/** This function waits until a condition gives null, and gives null, or the last reason after `within` ms. */\nconst until = async (within: number, check: () => Promise<string | null>): Promise<string | null> => {\n const end = Date.now() + within;\n let why = await check();\n while (why !== null && Date.now() < end) {\n await sleep(500);\n why = await check();\n }\n return why;\n};\n\n/** This is a ServiceL2Status of MetalLB: the node that announces a Service, and the time of the object. */\nexport interface Announcement {\n readonly node: string;\n readonly created: string | null;\n}\n\n/** This function gives the nodes that announce a Service, from the ServiceL2Status objects of the speakers. */\nexport const announcementsOf = async (cluster: Pick<Cluster, \"kubectl\">, namespace: string, service: string): Promise<Announcement[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"servicel2statuses.metallb.io\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{ metadata?: { creationTimestamp?: string }; status?: { node?: string; serviceName?: string; serviceNamespace?: string } }>;\n };\n return list.items\n .filter((item) => item.status?.serviceName === service && item.status.serviceNamespace === namespace && typeof item.status.node === \"string\")\n .map((item) => ({ node: item.status!.node!, created: item.metadata?.creationTimestamp ?? null }))\n .sort((a, b) => a.node.localeCompare(b.node));\n};\n\n/** This function gives the nodes with a ready speaker that is not deleted. */\nexport const speakersReady = async (cluster: Pick<Cluster, \"kubectl\">): Promise<string[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"pods\", \"-l\", \"app.kubernetes.io/component=speaker\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{ metadata: { deletionTimestamp?: string }; spec: { nodeName?: string }; status?: { containerStatuses?: ReadonlyArray<{ ready: boolean }> } }>;\n };\n return list.items\n .filter((pod) => pod.metadata.deletionTimestamp === undefined && pod.spec.nodeName !== undefined)\n .filter((pod) => (pod.status?.containerStatuses ?? []).length > 0 && pod.status!.containerStatuses!.every((status) => status.ready))\n .map((pod) => pod.spec.nodeName!)\n .sort();\n};\n\n/**\n * This function makes `owner` the only node that announces the Service, with a ready speaker on each\n * node of `nodes`. It gives null, or why it could not in `within` ms. After a heal, the other node\n * stops when it sees that the owner is alive again. The function then annotates the Service. Each\n * speaker reconciles it, and the owner sends its gratuitous ARPs for 5 s. Thus each client learns\n * the MAC address of the owner.\n */\nexport const holdMetallb = async (cluster: Pick<Cluster, \"kubectl\">, owner: string, nodes: readonly string[], namespace: string, service: string, within = 180_000): Promise<string | null> => {\n const ready = await until(within, async () => {\n const found = await speakersReady(cluster);\n const missing = nodes.filter((node) => !found.includes(node));\n return missing.length === 0 ? null : `the speaker of MetalLB is not ready on ${missing.join(\", \")}`;\n });\n if (ready !== null) return ready;\n const sole = await until(within, async () => {\n const found = (await announcementsOf(cluster, namespace, service)).map((each) => each.node);\n return found.length === 1 && found[0] === owner ? null : `the Service ${service} is announced by ${found.join(\", \") || \"no node\"}, not only by ${owner}`;\n });\n if (sole !== null) return sole;\n await cluster.kubectl([\"-n\", namespace, \"annotate\", \"service\", service, `${ANNOUNCE_ANNOTATION}=${Date.now()}`, \"--overwrite\"]);\n return null;\n};\n\n/**\n * This function installs MetalLB in a cluster, with its images on each node, the speakers on `nodes`,\n * and a pool of the VIP. The webhook of the controller checks the pool, thus the function tries the\n * pool again until the webhook answers. A failed install gives the pods of MetalLB and the tail of\n * their logs.\n *\n * With `memberlist`, the ConfigMap of the config file comes before the speakers. Then the function\n * checks that each speaker logged the load of the file (speakerlist.go, line 393). Without it, the\n * function checks that the ConfigMap does not exist.\n */\nexport const installMetallb = async (cluster: Cluster, images: MetallbImages, nodes: readonly string[], vip: string, memberlist?: MemberlistSettings): Promise<void> => {\n try {\n for (const node of nodes) await cluster.kubectl([\"label\", \"node\", node, `${VIP_LABEL}=1`, \"--overwrite\"]);\n if (memberlist !== undefined) {\n await cluster.apply([{ apiVersion: \"v1\", kind: \"Namespace\", metadata: { name: METALLB_NAMESPACE } }]);\n await cluster.apply([\n { apiVersion: \"v1\", kind: \"ConfigMap\", metadata: { name: MEMBERLIST_CONFIG, namespace: METALLB_NAMESPACE }, data: { \"config.json\": JSON.stringify(memberlistFileOf(memberlist)) } },\n ]);\n }\n await helmInstall(cluster, \"metallb\", METALLB_CHART, METALLB_NAMESPACE, metallbValues(images), { createNamespace: true });\n let failure: unknown = null;\n const end = Date.now() + 180_000;\n do {\n failure = await cluster.apply(metallbObjects(vip)).then(\n () => null,\n (error: unknown) => error,\n );\n if (failure !== null) await sleep(2000);\n } while (failure !== null && Date.now() < end);\n if (failure !== null) throw failure;\n if (memberlist === undefined) {\n const config = (await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"configmap\", MEMBERLIST_CONFIG, \"--ignore-not-found\", \"-o\", \"name\"])).trim();\n if (config !== \"\") throw new Error(`the ConfigMap ${MEMBERLIST_CONFIG} is there: memberlist would not use its defaults`);\n } else {\n const loaded = await until(120_000, async () => {\n const pods = JSON.parse(await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"pods\", \"-l\", \"app.kubernetes.io/component=speaker\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{ metadata: { name: string } }>;\n };\n if (pods.items.length !== nodes.length) return `${pods.items.length} speakers, not ${nodes.length}`;\n for (const pod of pods.items) {\n const log = await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"logs\", pod.metadata.name, \"-c\", \"speaker\"]).catch((error: unknown) => String(error));\n if (!log.includes(\"loading memberlist config from file\")) return `the speaker ${pod.metadata.name} did not log the load of the config file of memberlist`;\n }\n return null;\n });\n if (loaded !== null) throw new Error(loaded);\n }\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", METALLB_NAMESPACE, ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const logs = await state([\"logs\", \"-l\", \"app.kubernetes.io/name=metallb\", \"--tail=40\", \"--prefix\", \"--all-containers\"]);\n throw new Error(`the install of MetalLB failed: ${String(error)}\\nthe pods of MetalLB:\\n${pods}\\nthe logs:\\n${logs}`);\n }\n};\n\n/** These words mark the lines of a speaker that the record keeps: the events of memberlist, the announces and the withdraws. */\nconst KEPT = /node event|Marking|Suspect|Refuting|serviceAnnounced|serviceWithdrawn|memberlist/;\n\n/**\n * This function gives the record of MetalLB after a run: the nodes that announce the Service, and\n * some lines of each speaker. The lines are about memberlist and the announces, with the times of the kubelet.\n */\nexport const metallbRecord = async (cluster: Pick<Cluster, \"kubectl\">, namespace: string, service: string): Promise<Record<string, unknown>> => {\n const announcements = await announcementsOf(cluster, namespace, service);\n const text = await cluster\n .kubectl([\"-n\", METALLB_NAMESPACE, \"logs\", \"-l\", \"app.kubernetes.io/component=speaker\", \"-c\", \"speaker\", \"--since=300s\", \"--tail=4000\", \"--prefix\", \"--timestamps\"])\n .catch((error: unknown) => String(error));\n const log = text\n .split(\"\\n\")\n .filter((line) => KEPT.test(line))\n .slice(-600)\n .join(\"\\n\");\n // a run with a schedule of pauses (SYS-57) has several deaths and joins, thus the record keeps up to 600 lines\n return { announcements, log: log.slice(-90000) };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport type { KindCell, VipAnnouncerName, VipSettings } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { KUBE_VIP, KUBE_VIP_ARP_RATE, KUBE_VIP_IMAGE, KUBE_VIP_LEASE, holdVip, installKubeVip, vipLeaseOf, vipOf } from \"./kubevip.ts\";\nimport { METALLB_ANNOTATION, METALLB_IMAGES, METALLB_SPAM, holdMetallb, installMetallb, metallbRecord, metallbVipOf } from \"./metallb.ts\";\nimport { LB_CLASS, VIP_ANNOTATION } from \"./names.ts\";\n\n/**\n * This module gives one shape to each announcer of a VIP: kube-vip (SYS-51) and MetalLB in L2 mode\n * (SYS-53). The lab, the Service of the VIP and the clients outside the cluster are the same for\n * each one. Only the install, the choice of the VIP, the hold before each run and the record differ.\n */\n\n/** This is how the Service of a VIP selects its announcer: its class of load balancer, or none, and the annotation of its address. */\nexport interface VipService {\n readonly loadBalancerClass: string | null;\n readonly annotation: string;\n}\n\n/** An announcer is one way to give a Service of type LoadBalancer a VIP on the Docker network of the nodes. */\nexport interface VipAnnouncer {\n readonly name: VipAnnouncerName;\n /** These are the images of the announcer, pinned by digest, by their names in the manifest of a record. */\n readonly images: Readonly<Record<string, string>>;\n readonly service: VipService;\n /** This time is the most from the end of a hold to the first gratuitous ARP of the holder. */\n readonly settle: Quantity;\n /** This function gives the VIP of a subnet, with `owner` its holder among `nodes`. */\n readonly vipOf: (subnet: string, owner: string, nodes: readonly string[]) => string;\n /** This function installs the announcer, with the local tags of its images, by the names of `images`. MetalLB uses the settings of memberlist of `settings`. */\n readonly install: (cluster: Cluster, local: Readonly<Record<string, string>>, owner: string, nodes: readonly string[], vip: string, settings?: VipSettings) => Promise<void>;\n /** This function makes `owner` the holder of the VIP before a run. It gives null, or why not. */\n readonly hold: (cluster: Cluster, owner: string, nodes: readonly string[], namespace: string, service: string) => Promise<string | null>;\n /** This function gives the state of the announcer after a run, for the line `vip`. */\n readonly record: (cluster: Cluster, namespace: string, service: string) => Promise<Record<string, unknown>>;\n}\n\nconst local = (images: Readonly<Record<string, string>>, name: string): string => {\n const image = images[name];\n if (image === undefined) throw new Error(`the run has no local image of ${name}`);\n return image;\n};\n\n/** This announcer is kube-vip 1.3.1 in ARP mode, with one Lease for each VIP of a Service (`kubevip.ts`). */\nexport const KUBE_VIP_ANNOUNCER: VipAnnouncer = {\n name: \"kube-vip\",\n images: { [KUBE_VIP]: KUBE_VIP_IMAGE },\n service: { loadBalancerClass: LB_CLASS, annotation: VIP_ANNOTATION },\n settle: KUBE_VIP_ARP_RATE,\n vipOf: (subnet) => vipOf(subnet),\n install: (cluster, images, owner, nodes) => installKubeVip(cluster, local(images, KUBE_VIP), owner, nodes),\n hold: (cluster, owner, nodes) => holdVip(cluster, owner, nodes),\n record: async (cluster) => {\n const lease = await vipLeaseOf(cluster);\n const log = await cluster\n .kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"logs\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"--tail=30\", \"--prefix\", \"--timestamps\"])\n .catch((error: unknown) => String(error));\n return { lease, log: log.slice(-6000) };\n },\n};\n\n/** This announcer is MetalLB 0.16.1 in L2 mode, with its speakers in memberlist (`metallb.ts`). */\nexport const METALLB_ANNOUNCER: VipAnnouncer = {\n name: \"metallb\",\n images: { \"metallb-speaker\": METALLB_IMAGES.speaker, \"metallb-controller\": METALLB_IMAGES.controller },\n service: { loadBalancerClass: null, annotation: METALLB_ANNOTATION },\n settle: METALLB_SPAM.for,\n vipOf: (subnet, owner, nodes) => metallbVipOf(subnet, owner, nodes),\n install: (cluster, images, _owner, nodes, vip, settings) =>\n installMetallb(cluster, { speaker: local(images, \"metallb-speaker\"), controller: local(images, \"metallb-controller\") }, nodes, vip, settings?.memberlist),\n hold: (cluster, owner, nodes, namespace, service) => holdMetallb(cluster, owner, nodes, namespace, service),\n record: (cluster, namespace, service) => metallbRecord(cluster, namespace, service),\n};\n\n/** This function gives the announcer of the VIP of a cell: kube-vip when the cell names none. */\nexport const announcerOf = (cell: Pick<KindCell, \"vip\">): VipAnnouncer => (cell.vip?.announcer === \"metallb\" ? METALLB_ANNOUNCER : KUBE_VIP_ANNOUNCER);\n\n/** This function gives null if each client sends to `mac`, or the clients that do not. */\nexport const macsAt = (found: ReadonlyArray<{ readonly client: string; readonly mac: string | null }>, mac: string, count: number): string | null => {\n const wrong = found.filter((each) => each.mac !== mac);\n if (found.length !== count) return `${found.length} clients outside answer, not ${count}`;\n return wrong.length === 0 ? null : wrong.map((each) => `${each.client} has ${each.mac ?? \"no MAC\"}`).join(\", \");\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Backoff, KindCell } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport type { PinnedChart } from \"./helm.ts\";\nimport { helmInstall } from \"./helm.ts\";\nimport { LOG_LABEL } from \"./names.ts\";\n\n/**\n * This module installs Apache Pulsar 5.0.0 in a kind cluster (SYS-56): one ZooKeeper, one bookie and\n * two brokers, from the Helm chart 4.8.0. It also gives the producers of the cell: a Python script\n * in the image of Pulsar, with its client 3.13.0 over the C++ client 4.2.0. Each producer sends to\n * one topic, and a thread of it looks up the owner of that topic at each probe.\n *\n * The fault is a SIGKILL of the JVM of the broker that owns the topic. Each term of the return of a\n * producer comes from a pinned source:\n * - The owner of a bundle is an ephemeral znode of the session of its broker. A lookup gives the\n * owner from the cache of the ownership, with no check of its life (pulsar-broker\n * NamespaceService.java at v5.0.0, lines 489–508). Thus each lookup names the stopped broker\n * until its session expires.\n * - The session timeout is `metadataStoreSessionTimeoutMillis`, 30000 (conf/broker.conf at v5.0.0,\n * line 187, and the chart, values.yaml line 1474). ZKMetadataStore.java gives it to ZooKeeper (line 105).\n * - The client of ZooKeeper 3.9.6 pings after 9 s with no send (ClientCnxn.java, lines 422, 1184\n * and 1263–1268). That is half of the read timeout of 2/3 of the session, less 1 s.\n * The server rounds each expiry up to the next tick of 2000 ms (ExpiryQueue.java, lines 53–54\n * and 87, and conf/zookeeper.conf of Pulsar, line 21). Thus the session expires 21–32 s after the stop.\n * - The broker that stays then takes the bundle at the next lookup.\n * - The chart starts a broker again only after its old znode is gone (templates/broker-statefulset.yaml,\n * lines 296–302). Thus the stopped broker cannot take the bundle back first.\n */\n\n/** This version is the release of Pulsar of the lab. */\nexport const PULSAR_VERSION = \"5.0.0\";\n\n/** This chart is the Helm chart 4.8.0 of Pulsar. The sha256 is of the file of the release. */\nexport const PULSAR_CHART: PinnedChart = {\n name: \"pulsar\",\n version: \"4.8.0\",\n url: \"https://downloads.apache.org/pulsar/helm-chart/4.8.0/pulsar-4.8.0.tgz\",\n sha256: \"464f4a268463d187c9bc83b365b31de0b4a460533d948a04f846ba9b026068f5\",\n};\n\n/**\n * This image is Pulsar 5.0.0 for linux/amd64, pinned by the digest of the manifest of the platform.\n * It has ZooKeeper 3.9.6, BookKeeper, the broker, Python 3 and the Python client 3.13.0. The lab\n * pulls it by its digest, tags it (`localPin`), and loads it into each node.\n */\nexport const PULSAR_IMAGE = \"apachepulsar/pulsar:5.0.0@sha256:11e9a885121e16be91da77fdd3cfc3d4cbbece4700271d75e949585dbd3a5a5b\";\n\n/** This namespace holds Pulsar. The release of the chart is `pulsar` too. */\nexport const PULSAR_NAMESPACE = \"pulsar\";\n\n/** This name is the app of the producers of a cell. */\nexport const PRODUCER = \"producer\";\n\n/** This topic is the topic of the producers. */\nexport const PULSAR_TOPIC = \"persistent://public/default/lab\";\n\n/** This URL is the Service of the brokers for the binary protocol. */\nexport const PULSAR_SERVICE: string = `pulsar://pulsar-broker.${PULSAR_NAMESPACE}.svc.cluster.local:6650`;\n\n/** This URL is the lookup of the owner of the topic, on the Service of the brokers. */\nexport const PULSAR_LOOKUP: string = `http://pulsar-broker.${PULSAR_NAMESPACE}.svc.cluster.local:8080/lookup/v2/topic/persistent/public/default/lab`;\n\n/** This time is the session timeout of each broker in ZooKeeper (`metadataStoreSessionTimeoutMillis`). */\nexport const SESSION: Quantity = { value: 30, unit: \"s\" };\n\n/** This time is the tick of ZooKeeper (`tickTime`). The server rounds each expiry up to a tick. */\nexport const TICK: Quantity = { value: 2, unit: \"s\" };\n\n/** This time is the longest idle time of the client of ZooKeeper before its ping: the half of 2/3 of the session, less 1 s. */\nexport const PING: Quantity = { value: 9, unit: \"s\" };\n\n/**\n * This backoff is the backoff of a producer of the C++ client 4.2.0, with the defaults of the Python\n * client 3.13.0:\n * - The initial delay is 100 ms and the most is 60 s (lib/ClientConfigurationImpl.h, lines 39–40).\n * - The mandatory stop is the send timeout less 100 ms, and at least 100 ms (lib/ProducerImpl.cc,\n * lines 55–57). The send timeout of `create_producer` is 30000 ms (pulsar/__init__.py, line 1015).\n * - The jitter takes 0–9 % of each delay (lib/Backoff.cc, line 54).\n * - A try that fails takes the lookup and the connect, which a reset ends at once. The lab gives it 100 ms.\n */\nexport const PRODUCER_BACKOFF: Backoff = {\n initial: { value: 100, unit: \"ms\" },\n max: { value: 60, unit: \"s\" },\n mandatoryStop: { value: 29.9, unit: \"s\" },\n jitter: 0.09,\n attempt: { value: 100, unit: \"ms\" },\n};\n\n/** This function gives the repository and the tag of a local image, for the values of a chart. */\nconst repositoryOf = (local: string): { repository: string; tag: string } => {\n const at = local.lastIndexOf(\":\");\n if (at <= 0) throw new Error(`the image ${local} has no tag`);\n return { repository: local.slice(0, at), tag: local.slice(at + 1) };\n};\n\n/**\n * This function gives the values of the chart: ZooKeeper, one bookie and two brokers, with no\n * proxy, no autorecovery, no toolset and no monitor. The bookie keeps one copy of each entry, and\n * the brokers do not move a bundle by load. Each image is the local image, with no pull.\n */\nexport const pulsarValues = (image: string): Record<string, unknown> => {\n const { repository, tag } = repositoryOf(image);\n const noMonitor = { podMonitor: { enabled: false } };\n return {\n components: { zookeeper: true, bookkeeper: true, broker: true, autorecovery: false, proxy: false, toolset: false, functions: false, function_worker: false },\n \"victoria-metrics-k8s-stack\": { enabled: false },\n defaultPulsarImageRepository: repository,\n defaultPulsarImageTag: tag,\n defaultPullPolicy: \"Never\",\n affinity: { anti_affinity: false },\n volumes: { persistence: false },\n zookeeper: { replicaCount: 1, statefulsetUpgrade: { enabled: false }, ...noMonitor },\n bookkeeper: { replicaCount: 1, ...noMonitor },\n autorecovery: noMonitor,\n proxy: noMonitor,\n broker: {\n replicaCount: 2,\n statefulsetUpgrade: { enabled: false },\n ...noMonitor,\n configData: {\n managedLedgerDefaultEnsembleSize: \"1\",\n managedLedgerDefaultWriteQuorum: \"1\",\n managedLedgerDefaultAckQuorum: \"1\",\n loadBalancerSheddingEnabled: \"false\",\n loadBalancerAutoBundleSplitEnabled: \"false\",\n },\n },\n };\n};\n\n/**\n * This script is a producer of the cell. Each line that it writes is JSON, with the time of\n * CLOCK_MONOTONIC in milliseconds, the pod and the node:\n * - `send`: one send, at its start, with its time to the answer and its outcome. A send is `ok` within\n * the timeout, `late` after it, or the name of the result of the client (for example `timeout`).\n * - `lookup`: one lookup of the owner of the topic, with the host of the owner.\n * - `pulsar`: one line of the C++ client about the connection of the producer.\n */\nexport const PRODUCER_SCRIPT = `import json, logging, os, sys, threading, time, urllib.request\nimport pulsar\n\nPOD = os.environ[\"POD_NAME\"]\nNODE = os.environ[\"NODE_NAME\"]\nEVERY = int(os.environ[\"EVERY_MS\"]) / 1000\nTIMEOUT = int(os.environ[\"TIMEOUT_MS\"])\nPROBE = int(os.environ[\"PROBE_MS\"]) / 1000\nLOCK = threading.Lock()\nKEPT = (\"Schedule reconnection\", \"Getting connection from pool\", \"Created producer\", \"Connection closed\", \"Disconnected\", \"Lookup response\", \"Failed to\", \"Finished connecting\")\n\n\ndef now():\n return time.monotonic() * 1000\n\n\ndef say(**line):\n text = json.dumps({**line, \"p\": POD, \"n\": NODE}, separators=(\",\", \":\"))\n with LOCK:\n sys.stdout.write(text + \"\\\\n\")\n sys.stdout.flush()\n\n\nclass Relay(logging.Handler):\n def emit(self, record):\n message = record.getMessage()\n if any(word in message for word in KEPT):\n say(ev=\"pulsar\", t=now(), m=message[:400])\n\n\ndef probe():\n while True:\n start = now()\n owner = None\n outcome = \"ok\"\n try:\n with urllib.request.urlopen(os.environ[\"LOOKUP\"], timeout=1) as answer:\n data = json.loads(answer.read())\n url = data.get(\"brokerUrl\") or \"\"\n owner = url.split(\"://\", 1)[-1].rsplit(\":\", 1)[0] or None\n except Exception as error:\n outcome = type(error).__name__.lower()\n say(ev=\"lookup\", t=start, d=round(now() - start, 3), o=outcome, owner=owner)\n time.sleep(max(0.0, PROBE - (now() - start) / 1000))\n\n\ndef answered(start):\n def done(result, message_id):\n took = now() - start\n if result == pulsar.Result.Ok:\n outcome = \"ok\" if took <= TIMEOUT else \"late\"\n else:\n outcome = getattr(result, \"name\", str(result).split(\".\")[-1]).lower()\n say(ev=\"send\", t=start, d=round(took, 3), o=outcome)\n return done\n\n\nlogger = logging.getLogger(\"lab\")\nlogger.setLevel(logging.INFO)\nlogger.propagate = False\nlogger.addHandler(Relay())\nthreading.Thread(target=probe, daemon=True).start()\nclient = pulsar.Client(os.environ[\"SERVICE\"], logger=logger)\nwhile True:\n try:\n producer = client.create_producer(os.environ[\"TOPIC\"])\n break\n except Exception as error:\n say(ev=\"text\", t=now(), text=\"create_producer: \" + str(error)[:200])\n time.sleep(2)\nat = time.monotonic()\nwhile True:\n start = now()\n try:\n producer.send_async(b\"lab\", answered(start))\n except Exception as error:\n say(ev=\"send\", t=start, d=0, o=\"refused-\" + type(error).__name__.lower())\n at += EVERY\n time.sleep(max(0.0, at - time.monotonic()))\n`;\n\n/**\n * This function gives the producers of a cell in its namespace. They are the script in a ConfigMap,\n * and a Deployment of `clients` pods in the local image of Pulsar, one on each worker.\n */\nexport const pulsarObjects = (cell: KindCell, namespace: string, image: string): unknown[] => {\n if (cell.pulsar === undefined) throw new Error(`${cell.name} has no broker`);\n const env = {\n SERVICE: PULSAR_SERVICE,\n TOPIC: PULSAR_TOPIC,\n LOOKUP: PULSAR_LOOKUP,\n EVERY_MS: String(valueIn(cell.workload.every, \"ms\")),\n TIMEOUT_MS: String(valueIn(cell.workload.timeout, \"ms\")),\n PROBE_MS: String(valueIn(cell.pulsar.probe, \"ms\")),\n PYTHONUNBUFFERED: \"1\",\n };\n return [\n { apiVersion: \"v1\", kind: \"ConfigMap\", metadata: { name: PRODUCER, namespace }, data: { \"producer.py\": PRODUCER_SCRIPT } },\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: PRODUCER, namespace, labels: { app: PRODUCER } },\n spec: {\n replicas: cell.pulsar.clients,\n selector: { matchLabels: { app: PRODUCER } },\n template: {\n metadata: { labels: { app: PRODUCER, [LOG_LABEL]: \"1\" } },\n spec: {\n terminationGracePeriodSeconds: 0,\n topologySpreadConstraints: [{ maxSkew: 1, topologyKey: \"kubernetes.io/hostname\", whenUnsatisfiable: \"DoNotSchedule\", labelSelector: { matchLabels: { app: PRODUCER } } }],\n volumes: [{ name: \"script\", configMap: { name: PRODUCER } }],\n containers: [\n {\n name: PRODUCER,\n image,\n imagePullPolicy: \"Never\",\n command: [\"python3\", \"/lab/producer.py\"],\n volumeMounts: [{ name: \"script\", mountPath: \"/lab\" }],\n env: [\n ...Object.entries(env).map(([name, value]) => ({ name, value })),\n { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } },\n { name: \"NODE_NAME\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/** A broker is a pod of a broker: its name, its node, its address, and if it is ready. */\nexport interface Broker {\n readonly pod: string;\n readonly node: string;\n readonly ip: string;\n readonly ready: boolean;\n}\n\n/** This function gives the pods of the brokers, in the sequence of their names. */\nexport const brokersOf = async (cluster: Pick<Cluster, \"kubectl\">): Promise<Broker[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", PULSAR_NAMESPACE, \"get\", \"pods\", \"-l\", \"component=broker\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{\n metadata: { name: string; deletionTimestamp?: string };\n spec: { nodeName?: string };\n status?: { podIP?: string; containerStatuses?: ReadonlyArray<{ ready: boolean }> };\n }>;\n };\n return list.items\n .filter((pod) => pod.metadata.deletionTimestamp === undefined)\n .map((pod) => ({\n pod: pod.metadata.name,\n node: pod.spec.nodeName ?? \"\",\n ip: pod.status?.podIP ?? \"\",\n ready: (pod.status?.containerStatuses ?? []).length > 0 && pod.status!.containerStatuses!.every((status) => status.ready),\n }))\n .sort((a, b) => a.pod.localeCompare(b.pod));\n};\n\n/** This function gives the broker that a host of a lookup names. Its pod is the first label of the host, or its address is the host. */\nexport const brokerOfHost = (brokers: readonly Broker[], host: string): Broker | undefined =>\n brokers.find((broker) => host.split(\".\")[0] === broker.pod || host === broker.ip);\n\n/** This is the state of the producers that the hold reads. It has the last lookup and the last sends of each producer, and the last time of a line. */\nexport interface ProducerState {\n readonly owners: ReadonlyMap<string, { readonly t: number; readonly owner: string | null }>;\n readonly sends: ReadonlyMap<string, { readonly ok: number; readonly failed: number }>;\n readonly last: number;\n}\n\n/**\n * This function tells why the producers are not at rest, or gives the host of the owner of the\n * topic. At rest, each of `clients` producers had an answer in the last second and no failure in\n * the last 2 s. The last lookup of each one is in the last 2 s, and each names the same owner.\n */\nexport const ownerAtRest = (state: ProducerState, clients: number): { readonly owner: string } | string => {\n if (state.sends.size !== clients) return `${state.sends.size} producers send, not ${clients}`;\n for (const [pod, send] of state.sends) {\n if (state.last - send.ok > 1000) return `the producer ${pod} has no answer in the last second`;\n if (state.last - send.failed < 2000) return `the producer ${pod} failed in the last 2 s`;\n }\n const owners = new Set<string>();\n for (const pod of state.sends.keys()) {\n const seen = state.owners.get(pod);\n if (seen === undefined || seen.owner === null || state.last - seen.t > 2000) return `the producer ${pod} has no lookup with an owner in the last 2 s`;\n owners.add(seen.owner);\n }\n if (owners.size !== 1) return `the lookups name ${[...owners].join(\" and \")}`;\n return { owner: [...owners][0]! };\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * This function waits until each broker is ready and the producers are at rest (`ownerAtRest`). It\n * gives the broker that owns the topic and the host that the lookups name, or why it could not in `within` ms.\n */\nexport const holdPulsar = async (\n cluster: Pick<Cluster, \"kubectl\">,\n state: () => ProducerState,\n clients: number,\n within = 300_000,\n): Promise<{ readonly broker: Broker; readonly host: string } | string> => {\n const end = Date.now() + within;\n let why = \"the hold did not start\";\n do {\n const brokers = await brokersOf(cluster);\n const busy = brokers.find((broker) => !broker.ready);\n const rest = ownerAtRest(state(), clients);\n if (brokers.length !== 2) why = `${brokers.length} brokers, not 2`;\n else if (busy !== undefined) why = `the broker ${busy.pod} is not ready`;\n else if (typeof rest === \"string\") why = rest;\n else {\n const broker = brokerOfHost(brokers, rest.owner);\n if (broker !== undefined) return { broker, host: rest.owner };\n why = `the owner ${rest.owner} is no broker`;\n }\n await sleep(500);\n } while (Date.now() < end);\n return why;\n};\n\n/**\n * This function installs Pulsar in a cluster, with its image on each node. A failed install gives\n * the pods of Pulsar and the tail of their logs.\n */\nexport const installPulsar = async (cluster: Cluster, image: string): Promise<void> => {\n try {\n await helmInstall(cluster, \"pulsar\", PULSAR_CHART, PULSAR_NAMESPACE, pulsarValues(image), { createNamespace: true });\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", PULSAR_NAMESPACE, ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const logs = await state([\"logs\", \"-l\", \"app=pulsar\", \"--tail=30\", \"--prefix\", \"--all-containers\"]);\n throw new Error(`the install of Pulsar failed: ${String(error)}\\nthe pods of Pulsar:\\n${pods}\\nthe logs:\\n${logs}`);\n }\n};\n\n/** These words mark the lines of a broker that the record keeps: the sessions, the leader, the ownership and the topics. */\nconst KEPT = /[Ss]ession|[Ee]xpired|[Ll]eader|[Oo]wnership|[Bb]undle|Loaded|[Ll]oading topic|Created new producer|Disconnect|Closed connection|znode|ManagedLedger.*(open|recover)/;\n\n/**\n * This function gives the record of Pulsar after a run. It has the brokers, and the kept lines of\n * each broker and of its last container, with the times of the kubelet.\n */\nexport const pulsarRecord = async (cluster: Pick<Cluster, \"kubectl\">): Promise<Record<string, unknown>> => {\n const brokers = await brokersOf(cluster);\n const logs: Record<string, string> = {};\n for (const broker of brokers) {\n for (const previous of [false, true]) {\n const text = await cluster\n .kubectl([\"-n\", PULSAR_NAMESPACE, \"logs\", broker.pod, \"-c\", \"pulsar-broker\", \"--since=300s\", \"--tail=6000\", \"--timestamps\", ...(previous ? [\"--previous\"] : [])])\n .catch((error: unknown) => String(error));\n logs[`${broker.pod}${previous ? \", the last container\" : \"\"}`] = text\n .split(\"\\n\")\n .filter((line) => KEPT.test(line))\n .slice(-80)\n .join(\"\\n\")\n .slice(-8000);\n }\n }\n return { brokers, logs };\n};\n","// This module has no imports: the agent of the lab image imports it as it is.\n\n/**\n * This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS\n * moves (`/stats?filter=`, `source/server/admin/stats_params.cc` of Envoy v1.39.4). An update of a\n * listener counts `listener_modified`. An update in place, of its filter chains only, also counts\n * `listener_in_place_updated` (`listener_manager_impl.cc:644`, `:692`).\n */\nexport const LDS_FILTER = \"^listener_manager\\\\.listener_(in_place_updated|modified)$\";\n\n/** The listener counts are the two counters of LDS of one sidecar: each update (`mod`), and each update in place (`inPlace`). */\nexport interface ListenerCounts {\n readonly mod: number;\n readonly inPlace: number;\n}\n\n/**\n * This function gives the listener counts of the text of `/stats`, one `name: value` on each line\n * (`stats_render.cc:15`). It gives null when the text has no counter `listener_modified`.\n */\nexport const listenerCountsOf = (text: string): ListenerCounts | null => {\n let mod: number | null = null;\n let inPlace = 0;\n for (const line of text.split(\"\\n\")) {\n const match = /^listener_manager\\.listener_(in_place_updated|modified): (\\d+)$/.exec(line.trim());\n if (match === null) continue;\n if (match[1] === \"modified\") mod = Number(match[2]);\n else inPlace = Number(match[2]);\n }\n return mod === null ? null : { mod, inPlace };\n};\n","// The parser of the conntrack table of a node. The agent of each node reads the table with it, and\n// the image copies this file, thus it imports nothing.\n\n/** A flow is one conntrack entry of TCP: its state, its life in seconds, its original tuple and the source of its replies. */\nexport interface ConntrackFlow {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow, as `ip:port`. */\n readonly from: string;\n /** This address is the original destination of the flow, as `ip:port`: a ClusterIP before its DNAT. */\n readonly to: string;\n /** This address is the source of the replies, as `ip:port`: the backend that the DNAT of the entry chose. */\n readonly reply: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/** An entry is one conntrack entry of a TCP flow to the ClusterIP. It has a state, a life in seconds, a client and the backend of its NAT. */\nexport interface ConntrackEntry {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow: the client, as `ip:port`. */\n readonly client: string;\n /** This address is the source of the replies: the backend that the DNAT of the entry chose, as `ip:port`. */\n readonly backend: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/**\n * These states keep the NAT of an entry for a new SYN on the same port (nf_conntrack_proto_tcp.c at\n * v6.8). A SYN on an entry in TIME_WAIT removes the entry and looks up again (:1011-1027), thus that\n * SYN gets a backend from the rules. In each other state, the SYN goes to the backend of the entry.\n * A CLOSE that a reset from the server made does not reopen either.\n */\nexport const KEEPS_NAT: ReadonlySet<string> = new Set([\"SYN_SENT\", \"SYN_SENT2\", \"SYN_RECV\", \"ESTABLISHED\", \"FIN_WAIT\", \"CLOSE_WAIT\", \"LAST_ACK\", \"CLOSE\"]);\n\n/**\n * These are the lives of an entry in the states of a handshake, in seconds\n * (nf_conntrack_tcp_timeout_syn_sent and _syn_recv). A retransmission of the SYN does not renew the\n * life of SYN_SENT (nf_conntrack_proto_tcp.c at v6.8). Thus the life of such an entry tells the age\n * of its first SYN.\n */\nexport const HANDSHAKE_LIFE: Readonly<Record<string, number>> = { SYN_SENT: 120, SYN_RECV: 60 };\n\n/** This form is one TCP line of `/proc/net/nf_conntrack` or `conntrack -L`. It has the timeout and state, the original tuple, the flags and counters, then the reply tuple. */\nconst LINE = /\\btcp\\s+\\d+\\s+(\\d+)\\s+(\\S+)\\s+src=(\\S+) dst=(\\S+) sport=(\\d+) dport=(\\d+)(.*?)\\bsrc=(\\S+) dst=\\S+ sport=(\\d+) dport=\\d+/;\n\n/** This function gives the TCP flows of a dump of the conntrack table. */\nexport const flowsOf = (text: string): ConntrackFlow[] => {\n const out: ConntrackFlow[] = [];\n for (const line of text.split(\"\\n\")) {\n const match = LINE.exec(line);\n if (match === null) continue;\n out.push({\n ttl: Number(match[1]),\n state: match[2]!,\n from: `${match[3]}:${match[5]}`,\n to: `${match[4]}:${match[6]}`,\n reply: `${match[8]}:${match[9]}`,\n unreplied: match[7]!.includes(\"[UNREPLIED]\"),\n });\n }\n return out;\n};\n\n/** This function gives the TCP entries of a dump of the conntrack table whose original destination is the ClusterIP and port. */\nexport const entriesOf = (text: string, ip: string, port: number): ConntrackEntry[] =>\n flowsOf(text)\n .filter((flow) => flow.to === `${ip}:${port}`)\n .map((flow) => ({ state: flow.state, ttl: flow.ttl, client: flow.from, backend: flow.reply, unreplied: flow.unreplied }));\n\n/**\n * This function gives the entries that keep their NAT to a pod that is not a backend of the rules.\n * A new SYN from the client port of such an entry goes to that pod, whatever the rules say.\n */\nexport const staleOf = (entries: readonly ConntrackEntry[], backends: readonly string[]): ConntrackEntry[] =>\n entries.filter((entry) => KEEPS_NAT.has(entry.state) && !backends.includes(entry.backend));\n\n/** This function gives the count of entries that `conntrack -D` deleted, from its last line: \"N flow entries have been deleted\". */\nexport const deletedOf = (text: string): number => {\n const match = /(\\d+) flow entr(?:y|ies) ha(?:s|ve) been deleted/.exec(text);\n if (match === null) throw new Error(`conntrack -D gave no count: ${text.trim().slice(0, 200)}`);\n return Number(match[1]);\n};\n\n/**\n * This function gives the flows whose handshake stopped `age` seconds ago or more. Such a flow is\n * a SYN with no reply (SYN_SENT), or a SYN and its SYN-ACK with no ACK (SYN_RECV). It is a\n * connection that times out. On the node of the client, its reply address is the backend of its\n * DNAT. On the node of that backend, a SYN_SENT tells that the backend did not answer. There, a\n * SYN_RECV tells that its answer did not come back.\n */\nexport const stalledOf = (flows: readonly ConntrackFlow[], age: number): ConntrackFlow[] =>\n flows.filter((flow) => {\n const life = HANDSHAKE_LIFE[flow.state];\n if (life === undefined || (flow.state === \"SYN_SENT\" && !flow.unreplied)) return false;\n return flow.ttl <= life - age;\n });\n","import type { StartedTestContainer } from \"testcontainers\";\nimport { harnessImage } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { ServiceRule } from \"./backends.ts\";\nimport { deletedOf } from \"./conntrack.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { OUTSIDE } from \"./names.ts\";\nimport type { Line } from \"./observe.ts\";\nimport { isTunnelState } from \"./observe.ts\";\n\nexport type { Line } from \"./observe.ts\";\n\n/** A stamp brackets an act on CLOCK_MONOTONIC, in milliseconds. */\nexport interface Stamp {\n readonly t0: number;\n readonly t1: number;\n}\n\n/**\n * These kinds of kernel record stay in the raw record of a run. The lab also keeps each change of the\n * state of a socket of the HBONE port (`isTunnelState`): the tunnels of ztunnel. It counts the other\n * records and drops them.\n */\nexport const KEPT_KERNEL: ReadonlySet<string> = new Set([\"rst_tx\", \"rst_rx\", \"rst_v4\", \"error\", \"retrans\", \"begin\", \"fail\"]);\n\n/** This function tells if the lab keeps a kernel record of the prober. */\nexport const keptKernel = (record: Readonly<Record<string, unknown>>): boolean => KEPT_KERNEL.has(String(record[\"ev\"])) || isTunnelState(record);\n\n/** 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. */\nexport interface EdsSpec {\n readonly cluster: string;\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/** An LDS spec is the interval of the LDS pollers, and each server pod with its node and a PID in it. */\nexport interface LdsSpec {\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/**\n * This function gives the MAC address of `ip` in a table of /proc/net/arp, in lower case. It gives\n * null if the table has no row for `ip`, or if the row has the flags 0x0: no MAC yet. The ARP watch\n * of `client.mjs` reads the table the same way.\n */\nexport const arpMacOf = (table: string, ip: string): string | null => {\n for (const row of table.split(\"\\n\").slice(1)) {\n const [address, , flags, mac] = row.trim().split(/\\s+/);\n if (address === ip) return flags === \"0x0\" || mac === undefined ? null : mac.toLowerCase();\n }\n return null;\n};\n\n/** An outside spec is the clients outside the cluster: their target, interval, timeout and count, and the address whose MAC they watch. */\nexport interface OutsideSpec {\n readonly target: string;\n readonly every: Quantity;\n readonly timeout: Quantity;\n readonly count: number;\n readonly arp: string;\n}\n\n/** These are the sidecars of one node: the agent of the lab and the injector of the harness, in the PID namespace of the node. */\ninterface Sidecars {\n readonly agent: StartedTestContainer;\n readonly injector: StartedTestContainer;\n}\n\n/** A kind lab is the instruments of one cluster: a sidecar pair on each node, the watcher of the API and the prober. */\nexport interface KindLab {\n readonly cluster: Cluster;\n readonly image: string;\n /** These are the lines since the last `take()`. */\n readonly lines: Line[];\n /** This record counts the kernel records that the lab dropped, by kind. */\n readonly dropped: Record<string, number>;\n /** This method gives the names of the containers that the lab started: they are not the lab containers of another agent. */\n own(): string[];\n /** This method gives the lines since the last call, and starts a new buffer. */\n take(): Line[];\n /** This method adds a listener of each line, and gives the function that removes it. */\n listen(listener: (line: Line) => void): () => void;\n /** This method adds a line of the runner to the lines: for example, evidence that the runner reads with a tool. */\n record(line: Line): void;\n /**\n * This method starts the watcher of the API on the namespaces. With `ztunnel`, it also follows the\n * log of each ztunnel in istio-system: lines `ztunnel-pod` and `ztunnel`, which no tracker of the\n * cell reads.\n */\n watch(namespaces: readonly string[], options?: { readonly ztunnel?: boolean | undefined }): Promise<void>;\n probe(netns: readonly number[]): Promise<void>;\n stopProbe(): Promise<void>;\n /**\n * This method starts the clients outside the cluster of a VIP (`KindCell.vip`). Each one is a\n * container of the lab image on the Docker network of the nodes, and runs `client.mjs`. Its lines\n * have the node `outside`, and a line `arp` at each change of the MAC address of the VIP. A\n * call stops the clients of the last call first. With null, it only stops them.\n */\n outside(spec: OutsideSpec | null): Promise<void>;\n /** This method gives the MAC address of `ip` in the neighbour table of each client outside the cluster. It is null for a client with no MAC for it. */\n outsideMacs(ip: string): Promise<ReadonlyArray<{ readonly client: string; readonly mac: string | null }>>;\n /** This method asks each agent to poll the backends of a Service in the rules of its node, `every` apart, or to stop with null. */\n rules(rule: (ServiceRule & { readonly every: Quantity }) | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each client pod of its node, `every` apart.\n * Each poll gives the hosts of one cluster. With null, the polls stop. A pod gives the PID of one\n * of its processes, and the agent enters the network namespace of that PID.\n */\n eds(spec: EdsSpec | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each server pod of its node, `every` apart.\n * Each poll gives the counters of LDS updates of that sidecar (`lds.ts`). With null, the polls stop.\n */\n lds(spec: LdsSpec | null): Promise<void>;\n act(node: string, spec: Readonly<Record<string, unknown>>): Promise<Stamp>;\n inject(node: string, pid: number, args: readonly string[]): Promise<Stamp>;\n /**\n * This method gives the rules of one table of iptables in the network namespace of a PID, with\n * their counters (`iptables-save -c`). The injector reads them. The method also adds them to the\n * lines as a line `tables`: evidence of which rules matched.\n */\n tables(node: string, pid: number, table: string): Promise<string>;\n /**\n * This method runs commands of the injector of the harness in the network namespace of a PID. Each\n * command is a sysctl or a rule of iptables, in sequence. It stops at the first command that fails. Each word of\n * a command is plain: letters, digits and `_ . , : = / + -`. The method adds the commands and their\n * output to the lines as a line `act`, and gives the output.\n */\n netns(node: string, pid: number, commands: ReadonlyArray<readonly string[]>): Promise<string>;\n now(): Promise<number>;\n pidOf(node: string, namespace: string, pod: string, container: string): Promise<number>;\n netnsOf(node: string, pid: number): Promise<number>;\n /** This method gives the path of the cgroup of a process, as the agent reaches it through the root of the node. */\n cgroupOf(node: string, pid: number): Promise<string>;\n /**\n * This method deletes the conntrack entries in SYN_SENT to a ClusterIP and port on each node, and\n * gives the count of each node. Such an entry is a SYN with no answer. It keeps its NAT for 120 s,\n * and a new SYN from the same port goes where it went.\n */\n forgetHandshakes(ip: string, port: number): Promise<Record<string, number>>;\n close(): Promise<void>;\n}\n\n/** This container class can share the process namespace of another container. */\nconst shared = async () => {\n const { GenericContainer } = await import(\"testcontainers\");\n return class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n};\n\n/** This function collects the JSON lines of a stream, and gives each one to `take`. */\nconst consume = (take: (line: Record<string, unknown>) => void) => (stream: NodeJS.ReadableStream) => {\n let partial = \"\";\n stream.on(\"data\", (chunk: Buffer | string) => {\n partial += chunk.toString();\n const split = partial.split(\"\\n\");\n partial = split.pop() ?? \"\";\n for (const text of split) {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"{\")) continue;\n try {\n take(JSON.parse(trimmed) as Record<string, unknown>);\n } catch {\n // a line that is not whole JSON is not a record\n }\n }\n });\n};\n\n/** This function gives a stamp of the injector of the harness, in nanoseconds, as milliseconds. */\nconst injectorStamp = (stdout: string): Stamp => {\n const stamp = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { t0: number | string; t1: number | string };\n return { t0: Number(BigInt(stamp.t0)) / 1e6, t1: Number(BigInt(stamp.t1)) / 1e6 };\n};\n\nconst output = async (container: StartedTestContainer, argv: readonly string[]): Promise<string> => {\n const result = await container.exec([...argv]);\n if (result.exitCode !== 0) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 500)}`);\n return result.stdout;\n};\n\n/**\n * This function opens the instruments of a cluster. Each node gets the agent of the lab and the\n * injector of the harness, privileged and in the PID namespace of the node. Thus the agent signals\n * a process of a pod by its PID on the node. The injector enters the network namespace of a pod\n * with `nsenter`.\n */\nexport const openKindLab = async (cluster: Cluster, image: string): Promise<KindLab> => {\n const harness = await harnessImage();\n const Shared = await shared();\n const { GenericContainer } = await import(\"testcontainers\");\n let lines: Line[] = [];\n const dropped: Record<string, number> = {};\n const listeners = new Set<(line: Line) => void>();\n const push = (line: Line): void => {\n lines.push(line);\n for (const listener of listeners) listener(line);\n };\n const sidecars = new Map<string, Sidecars>();\n const opened: StartedTestContainer[] = [];\n let watcher: StartedTestContainer | null = null;\n let prober: StartedTestContainer | null = null;\n let outsiders: StartedTestContainer[] = [];\n const stopOutside = async (): Promise<void> => {\n for (const container of outsiders) await container.stop({ timeout: 0 }).catch(() => undefined);\n outsiders = [];\n };\n try {\n for (const node of cluster.nodes) {\n const agent = await new Shared(image)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withEnvironment({ NODE_NAME: node })\n .withCommand([\"node\", \"agent.mjs\", \"serve\"])\n .withLogConsumer(consume((line) => push({ ...(line as unknown as Line), node })))\n .start();\n opened.push(agent);\n const injector = await new Shared(harness)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .start();\n opened.push(injector);\n sidecars.set(node, { agent, injector });\n }\n } catch (error) {\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n throw error;\n }\n const sidecarOf = (node: string): Sidecars => {\n const found = sidecars.get(node);\n if (found === undefined) throw new Error(`the cluster has no node ${node}`);\n return found;\n };\n const firstNode = cluster.nodes[0]!;\n /**\n * This function writes the file of the pollers of one kind on each agent: `fields`, the interval\n * and the pods of its node. A node without a pod, or a spec of null, gets no file.\n */\n const pollersOf = async (\n kind: \"eds\" | \"lds\",\n fields: Readonly<Record<string, unknown>> | null,\n spec: { readonly every: Quantity; readonly pods: LdsSpec[\"pods\"] } | null,\n ): Promise<void> => {\n for (const [node, { agent }] of sidecars) {\n const pods = spec === null ? [] : spec.pods.filter((pod) => pod.node === node).map(({ pod, pid }) => ({ pod, pid }));\n // the agent reads the interval of its poll in milliseconds\n const file = spec === null || pods.length === 0 ? null : JSON.stringify({ ...fields, every: valueIn(spec.every, \"ms\"), pods });\n if (file === null) await output(agent, [\"rm\", \"-f\", `/tmp/${kind}.json`]);\n // a rename is atomic, thus the agent never reads a part of the file\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/${kind}.json.partial && mv /tmp/${kind}.json.partial /tmp/${kind}.json`]);\n }\n };\n const lab: KindLab = {\n cluster,\n image,\n get lines() {\n return lines;\n },\n dropped,\n own() {\n return [...opened, ...(watcher === null ? [] : [watcher]), ...(prober === null ? [] : [prober]), ...outsiders].map((container) => container.getName().replace(/^\\//, \"\"));\n },\n take() {\n const out = lines;\n lines = [];\n return out;\n },\n record(line) {\n push(line);\n },\n listen(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async watch(namespaces, options) {\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n const kube = await cluster.credentials();\n watcher = await new GenericContainer(image)\n .withNetworkMode(cluster.network)\n .withEnvironment({ KUBE: JSON.stringify(kube), NAMESPACES: namespaces.join(\",\"), ZTUNNEL: options?.ztunnel === true ? \"1\" : \"0\" })\n .withCommand([\"node\", \"watch.mjs\"])\n .withLogConsumer(consume((line) => push(line as unknown as Line)))\n .start();\n },\n async probe(netns) {\n await lab.stopProbe();\n let begun: (ok: boolean, why: string) => void = () => {};\n const ready = new Promise<void>((resolve, reject) => {\n begun = (ok, why) => (ok ? resolve() : reject(new Error(`the prober failed: ${why}`)));\n });\n prober = await new GenericContainer(harness)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withCommand([\"/opt/harness/prober\", ...netns.map(String)])\n .withLogConsumer(\n consume((record) => {\n const ev = String(record[\"ev\"]);\n if (ev === \"begin\") begun(true, \"\");\n if (ev === \"fail\") begun(false, String(record[\"why\"]));\n if (!keptKernel(record) && ev !== \"clock\") {\n dropped[ev] = (dropped[ev] ?? 0) + 1;\n return;\n }\n const mono = Number(record[\"mono\"] ?? 0);\n push({ ...(record as unknown as Line), ev: `kernel.${ev}`, t: mono / 1e6 });\n }),\n )\n .start();\n await Promise.race([\n ready,\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"no begin line from the prober in 60 s\")), 60_000)),\n ]);\n },\n async outside(spec) {\n await stopOutside();\n if (spec === null) return;\n for (let k = 0; k < spec.count; k += 1) {\n const client = await new GenericContainer(image)\n .withNetworkMode(cluster.network)\n .withEnvironment({\n TARGET: spec.target,\n EVERY_MS: String(valueIn(spec.every, \"ms\")),\n TIMEOUT_MS: String(valueIn(spec.timeout, \"ms\")),\n POD_NAME: `${OUTSIDE}-${k}`,\n NODE_NAME: OUTSIDE,\n ARP_WATCH: spec.arp,\n })\n .withCommand([\"node\", \"client.mjs\"])\n .withLogConsumer(consume((line) => push(line as unknown as Line)))\n .start();\n outsiders.push(client);\n }\n },\n async outsideMacs(ip) {\n const found: Array<{ readonly client: string; readonly mac: string | null }> = [];\n for (const [k, client] of outsiders.entries()) {\n const { output } = await client.exec([\"cat\", \"/proc/net/arp\"]);\n found.push({ client: `${OUTSIDE}-${k}`, mac: arpMacOf(output, ip) });\n }\n return found;\n },\n async stopProbe() {\n if (prober !== null) await prober.stop({ timeout: 0 }).catch(() => undefined);\n prober = null;\n },\n async rules(rule) {\n // the agent reads the interval of its poll in milliseconds\n const file = rule === null ? null : JSON.stringify({ ...rule, every: valueIn(rule.every, \"ms\") });\n for (const { agent } of sidecars.values()) {\n if (file === null) await output(agent, [\"rm\", \"-f\", \"/tmp/rules.json\"]);\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/rules.json`]);\n }\n },\n async eds(spec) {\n await pollersOf(\"eds\", spec === null ? null : { cluster: spec.cluster }, spec);\n },\n async lds(spec) {\n await pollersOf(\"lds\", {}, spec);\n },\n async forgetHandshakes(ip, port) {\n const out: Record<string, number> = {};\n for (const [node, { agent }] of sidecars) {\n const argv = [\"nsenter\", \"-t\", \"1\", \"-m\", \"-n\", \"--\", \"conntrack\", \"-D\", \"-p\", \"tcp\", \"--orig-dst\", ip, \"--dport\", String(port), \"--state\", \"SYN_SENT\"];\n // the tool writes its count to stderr, and exits 1 when it deletes no entry\n const result = await agent.exec(argv);\n if (result.exitCode !== 0 && result.exitCode !== 1) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 300)}`);\n out[node] = deletedOf(result.output);\n }\n return out;\n },\n async act(node, spec) {\n const stdout = await output(sidecarOf(node).agent, [\"node\", \"agent.mjs\", \"act\", JSON.stringify(spec)]);\n const result = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { ok: boolean; t0: number; t1: number; why?: string };\n if (!result.ok) throw new Error(`the act ${JSON.stringify(spec)} on ${node} failed: ${result.why}`);\n push({ ev: \"act\", t: result.t0, t1: result.t1, node, spec });\n return { t0: result.t0, t1: result.t1 };\n },\n async inject(node, pid, args) {\n const stdout = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"/opt/harness/inject\", ...args]);\n const stamp = injectorStamp(stdout);\n push({ ev: \"act\", t: stamp.t0, t1: stamp.t1, node, spec: { op: \"inject\", pid, args } });\n return stamp;\n },\n async tables(node, pid, table) {\n const t = await lab.now();\n const text = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"iptables-save\", \"-c\", \"-t\", table]);\n push({ ev: \"tables\", t, node, pid, table, text: text.slice(0, 8000) });\n return text;\n },\n async netns(node, pid, commands) {\n // the words go to a shell as they are, thus each one is plain\n for (const word of commands.flat()) if (!/^[A-Za-z0-9_.,:=/+-]+$/.test(word)) throw new Error(`not a plain word of a command: ${word}`);\n if (commands.length === 0) throw new Error(\"no command\");\n const t = await lab.now();\n const text = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"sh\", \"-c\", commands.map((argv) => argv.join(\" \")).join(\" && \")]);\n push({ ev: \"act\", t, t1: await lab.now(), node, spec: { op: \"netns\", pid, commands }, out: text.slice(0, 2000) });\n return text;\n },\n async now() {\n return (await lab.act(firstNode, { op: \"now\" })).t0;\n },\n async pidOf(node, namespace, pod, container) {\n const ids = (\n await cluster.exec(node, [\n \"crictl\",\n \"ps\",\n \"-q\",\n \"--state\",\n \"running\",\n \"--label\",\n `io.kubernetes.pod.namespace=${namespace}`,\n \"--label\",\n `io.kubernetes.pod.name=${pod}`,\n \"--name\",\n `^${container}$`,\n ])\n )\n .split(/\\s+/)\n .filter((id) => id !== \"\");\n if (ids.length !== 1) throw new Error(`${namespace}/${pod}/${container} on ${node} has ${ids.length} running containers, not 1`);\n const info = JSON.parse(await cluster.exec(node, [\"crictl\", \"inspect\", ids[0]!])) as { info?: { pid?: number } };\n const pid = info.info?.pid;\n if (pid === undefined || !(pid > 1)) throw new Error(`crictl gives no PID for ${namespace}/${pod}/${container}`);\n return pid;\n },\n async netnsOf(node, pid) {\n const link = (await cluster.exec(node, [\"readlink\", `/proc/${pid}/ns/net`])).trim();\n const inode = /^net:\\[(\\d+)\\]$/.exec(link)?.[1];\n if (inode === undefined) throw new Error(`not a network namespace: ${link}`);\n return Number(inode);\n },\n async cgroupOf(node, pid) {\n const text = (await cluster.exec(node, [\"cat\", `/proc/${pid}/cgroup`])).trim();\n const path = /^0::(\\S+)$/m.exec(text)?.[1];\n // an empty or a short path is the root of the node: a write there throttles or freezes each pod of the node\n if (path === undefined || !/\\/cri-containerd-[0-9a-f]{64}\\.scope$/.test(path))\n throw new Error(`the cgroup of ${pid} on ${node} is not the scope of one container: ${text}`);\n return `/proc/1/root/sys/fs/cgroup${path}`;\n },\n async close() {\n await lab.stopProbe();\n await stopOutside();\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n watcher = null;\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n },\n };\n return lab;\n};\n\n/** This function pins a Docker image by digest under a local tag that holds the digest, for `kind load` and `imagePullPolicy: Never`. */\nexport const localPin = async (image: string): Promise<string> => {\n const match = /^([^@]+?)(?::([^@:/]+))?@sha256:([0-9a-f]{64})$/.exec(image);\n if (match === null) throw new Error(`${image} is not pinned by a digest`);\n const [, repository, tag, digest] = match;\n const local = `systems-lab/${repository!.split(\"/\").at(-1)}:${tag ?? \"pinned\"}-${digest!.slice(0, 12)}`;\n const present = await command(\"docker\", [\"image\", \"inspect\", \"--format\", \"{{json .RepoDigests}}\", `${repository}@sha256:${digest}`]).catch(() => \"\");\n if (!present.includes(digest!)) await command(\"docker\", [\"pull\", `${repository}@sha256:${digest}`], { timeout: 900_000 });\n await command(\"docker\", [\"tag\", `${repository}@sha256:${digest}`, local]);\n return local;\n};\n","import { createHash } from \"node:crypto\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell, KubeletProbe, MeshMode, Stack } from \"./cell.ts\";\nimport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\nimport type { VipService } from \"./announcer.ts\";\nimport { announcerOf } from \"./announcer.ts\";\nimport { LB_CLASS, VIP_ANNOTATION } from \"./names.ts\";\nimport { ZONE_LABEL, ZONE_LOST } from \"./scenario.ts\";\n\n/**\n * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the\n * server as a Deployment behind a Service, and one client on each node. The server is the service\n * or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout\n * strategy and the probe fields state the defaults of Kubernetes as values.\n */\n\n/** This function gives the stack of a cell: the service, when the cell names no stack. */\nexport const stackOf = (cell: Pick<KindCell, \"stack\">): Stack => cell.stack ?? \"service\";\n\nexport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\n\n/** This image is Redis 7.4, the stock target of the harness, pinned by digest. */\nexport const REDIS_IMAGE = \"redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f\";\n\nconst whole = (amount: Quantity, what: string): number => {\n const seconds = valueIn(amount, \"s\");\n if (!Number.isInteger(seconds) || seconds < 0) throw new RangeError(`${what} is ${seconds} s, not a whole number of seconds`);\n return seconds;\n};\n\n/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */\nexport const namespaceOf = (cell: Pick<KindCell, \"name\">): string =>\n `lab-${createHash(\"sha256\").update(cell.name).digest(\"hex\").slice(0, 10)}`;\n\n/** These are the labels of the namespace of each mode of a mesh. */\nconst MESH_LABELS: Readonly<Record<MeshMode, Readonly<Record<string, string>>>> = {\n sidecar: { \"istio-injection\": \"enabled\" },\n ambient: { \"istio.io/dataplane-mode\": \"ambient\" },\n};\n\n/**\n * This function gives the Namespace object of a cell. In the sidecar mode of a mesh, the label\n * `istio-injection=enabled` makes the injector of Istio put a sidecar in each pod of the namespace.\n * In the ambient mode, the label `istio.io/dataplane-mode=ambient` makes istio-cni redirect the\n * traffic of each pod to ztunnel. The listeners of ztunnel are in the network namespace of the pod.\n */\nexport const namespaceObject = (cell: Pick<KindCell, \"mesh\">, namespace: string): Record<string, unknown> => ({\n apiVersion: \"v1\",\n kind: \"Namespace\",\n metadata: { name: namespace, ...(cell.mesh === undefined ? {} : { labels: { ...MESH_LABELS[cell.mesh.mode] } }) },\n});\n\n/**\n * This function gives a probe of the pod, with each field of the cell. The probe of the service is an\n * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the\n * kubelet. It is a Check of the service \"\" on the port of the server, and the answer is SERVING until\n * GracefulStop. A gRPC probe can name another service (`service`): the field `service` of `GRPCAction`\n * (`staging/src/k8s.io/api/core/v1/types.go` at v1.37.0, line 2892).\n */\nexport const probeOf = (path: string | null, probe: KubeletProbe, service?: string): Record<string, unknown> => ({\n ...(path === null ? { grpc: service === undefined ? { port: PORT } : { port: PORT, service } } : { httpGet: { path, port: HEALTH_PORT } }),\n initialDelaySeconds: 0,\n periodSeconds: whole(probe.period, `the period of ${path ?? \"the gRPC probe\"}`),\n timeoutSeconds: whole(probe.timeout, `the timeout of ${path ?? \"the gRPC probe\"}`),\n failureThreshold: probe.threshold,\n successThreshold: 1,\n});\n\n/** This variable is the name of the pod, from the API: the service and grpc-go put it in their lines. */\nconst POD_ENV = { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } };\n\n/**\n * This function gives the environment of the app: its config as JSON, and the name of its pod. With\n * `failsRoute`, the route fails while the flag file is there (`cannot-serve`).\n */\nconst appEnv = (cell: KindCell): Array<Record<string, unknown>> => [\n {\n name: \"CONFIG\",\n value: JSON.stringify({\n name: SERVER,\n port: PORT,\n healthPort: HEALTH_PORT,\n declaration: cell.declaration,\n ...(cell.trigger.kind === \"cannot-serve\" && cell.trigger.serves !== true ? { failsRoute: true } : {}),\n }),\n },\n POD_ENV,\n];\n\n/** This file is the flag of the trigger `liveness-restart`, in the root of the server process (`run.ts`, the step `flag`). */\nconst FLAG_FILE = \"/tmp/lab-fail\";\n\n/**\n * This function gives the container of the server of a cell. The service runs `app.mjs` of the lab\n * image with its declaration. The server grpc-go runs from the image of the harness. Its lines name\n * the pod and stamp in milliseconds. Its replies name the pod in the header `x-pod`. Kubernetes\n * expands `$(POD_NAME)` in the command.\n *\n * The seam of `liveness-restart` in grpc-go is its service `liveness`, which the flag file fails\n * (`--fail-file`). Thus for that trigger the liveness probe is a Check of `liveness`. The readiness\n * probe stays a Check of \"\", which the flag file does not fail.\n */\nconst serverContainer = (cell: KindCell, version: number): Record<string, unknown> => {\n const env = { name: \"LAB_VERSION\", value: String(version) };\n if (stackOf(cell) === \"service\")\n return {\n command: [\"node\", \"app.mjs\"],\n env: [...appEnv(cell), env],\n ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(\"/startup\", cell.pod.startup) }),\n readinessProbe: probeOf(\"/readiness\", cell.pod.readiness),\n livenessProbe: probeOf(\"/liveness\", cell.pod.liveness),\n };\n const seam = cell.trigger.kind === \"liveness-restart\";\n return {\n command: [\"grpc-go-server\", \"--port\", String(PORT), \"--name\", \"$(POD_NAME)\", \"--clock\", \"ms\", ...(seam ? [\"--fail-file\", FLAG_FILE] : [])],\n env: [POD_ENV, env],\n ports: [{ containerPort: PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(null, cell.pod.startup) }),\n readinessProbe: probeOf(null, cell.pod.readiness),\n livenessProbe: seam ? probeOf(null, cell.pod.liveness, \"liveness\") : probeOf(null, cell.pod.liveness),\n };\n};\n\n/** These are the objects of Redis: one replica behind a Service, with no persistence. */\nexport const redisObjects = (namespace: string, image: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: REDIS, namespace, labels: { app: REDIS } },\n spec: {\n replicas: 1,\n selector: { matchLabels: { app: REDIS } },\n template: {\n metadata: { labels: { app: REDIS } },\n spec: {\n terminationGracePeriodSeconds: 1,\n containers: [\n {\n name: REDIS,\n image,\n imagePullPolicy: \"Never\",\n command: [\"redis-server\", \"--save\", \"\", \"--appendonly\", \"no\"],\n ports: [{ containerPort: 6379 }],\n readinessProbe: { tcpSocket: { port: 6379 }, periodSeconds: 1, failureThreshold: 1, successThreshold: 1, timeoutSeconds: 1 },\n },\n ],\n },\n },\n },\n },\n {\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: REDIS, namespace },\n spec: { selector: { app: REDIS }, ports: [{ port: 6379, targetPort: 6379 }] },\n },\n];\n\n/**\n * This function gives the preStop hook of a sleep, or nothing for no sleep. The hook is the native\n * sleep action of the kubelet. The API server refuses a sleep action that is longer than the grace\n * (`validateSleepAction`, `pkg/apis/core/validation/validation.go` at v1.37.0, line 3483). Thus a\n * longer sleep is a command in the container, `sleep` from `/bin/sh`, as public manifests write it.\n * The kubelet starts that command with no timeout (`pkg/kubelet/lifecycle/handlers.go`, line 81).\n */\nconst preStopOf = (preStop: number, grace: number): Record<string, unknown> => {\n if (preStop === 0) return {};\n if (preStop <= grace) return { lifecycle: { preStop: { sleep: { seconds: preStop } } } };\n return { lifecycle: { preStop: { exec: { command: [\"/bin/sh\", \"-c\", `sleep ${preStop}`] } } } };\n};\n\n/** This affinity keeps a pod in the zone that a zone loss keeps. */\nconst keptZone = {\n nodeAffinity: {\n requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [{ matchExpressions: [{ key: ZONE_LABEL, operator: \"NotIn\", values: [ZONE_LOST] }] }] },\n },\n};\n\n/**\n * This function gives the Service of a VIP (`announcer.ts`). It is of type LoadBalancer, with the\n * class of its announcer, if the announcer has one. The annotation of the announcer has the address\n * of the VIP. Without `service`, the announcer is kube-vip. The policy `Cluster` lets each node\n * send a request to a replica on another node. The holder writes the address to the status of the\n * Service. Then kube-proxy has the rules of the address on each node.\n */\nexport const vipService = (namespace: string, vip: string, ports: readonly unknown[], service: VipService = { loadBalancerClass: LB_CLASS, annotation: VIP_ANNOTATION }): unknown => ({\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: SERVER, namespace, annotations: { [service.annotation]: vip } },\n spec: {\n type: \"LoadBalancer\",\n ...(service.loadBalancerClass === null ? {} : { loadBalancerClass: service.loadBalancerClass }),\n externalTrafficPolicy: \"Cluster\",\n selector: { app: SERVER },\n ports,\n },\n});\n\n/**\n * These are the objects of the server: a Service of type ClusterIP, and the Deployment with the\n * settings of the cell. The image is the lab image for the service, or the image of grpc-go.\n *\n * With a VIP (`KindCell.vip`), the Service is of type LoadBalancer, with the address `vip` and the\n * class of its announcer (`vipService`). No replica is in the lost zone. Thus a zone loss takes away\n * only the holder of the VIP.\n */\nexport const serverObjects = (cell: KindCell, namespace: string, image: string, version = 1, vip: string | null = null): unknown[] => {\n const preStop = whole(cell.pod.preStop, \"the preStop sleep\");\n const grace = whole(cell.pod.grace, \"the grace period\");\n if (cell.vip !== undefined && vip === null) throw new Error(`${cell.name}: a cell with a VIP needs the address of the VIP`);\n const ports = [{ name: stackOf(cell) === \"grpc\" ? \"grpc\" : \"http\", port: 80, targetPort: PORT }];\n const zoneLoss = cell.trigger.kind === \"zone-loss\";\n return [\n vip === null\n ? { apiVersion: \"v1\", kind: \"Service\", metadata: { name: SERVER, namespace }, spec: { selector: { app: SERVER }, ports } }\n : vipService(namespace, vip, ports, announcerOf(cell).service),\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: SERVER, namespace, labels: { app: SERVER } },\n spec: {\n replicas: cell.pod.replicas,\n selector: { matchLabels: { app: SERVER } },\n strategy: { type: \"RollingUpdate\", rollingUpdate: { maxSurge: \"25%\", maxUnavailable: \"25%\" } },\n minReadySeconds: 0,\n template: {\n metadata: { labels: { app: SERVER, [LOG_LABEL]: \"1\", \"lab.systems/version\": String(version) } },\n spec: {\n terminationGracePeriodSeconds: grace,\n enableServiceLinks: true,\n topologySpreadConstraints: [\n {\n maxSkew: 1,\n topologyKey: \"kubernetes.io/hostname\",\n whenUnsatisfiable: \"ScheduleAnyway\",\n labelSelector: { matchLabels: { app: SERVER } },\n },\n // a zone loss needs a replica in each zone: the scheduler must keep the spread\n ...(zoneLoss && cell.vip === undefined\n ? [{ maxSkew: 1, topologyKey: ZONE_LABEL, whenUnsatisfiable: \"DoNotSchedule\", labelSelector: { matchLabels: { app: SERVER } } }]\n : []),\n ],\n // with a VIP, the replicas are only in the zone that stays: the loss takes away only the holder of the VIP\n ...(zoneLoss && cell.vip !== undefined ? { affinity: keptZone } : {}),\n containers: [\n {\n name: \"app\",\n image,\n imagePullPolicy: \"Never\",\n ...serverContainer(cell, version),\n ...preStopOf(preStop, grace),\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/**\n * These are the objects of the clients: a DaemonSet with one client on each node, the control plane\n * too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.\n * Each one is in the lab image, and each one writes the same lines. A service with the protocol\n * `h2c` gives its clients `PROTOCOL`, thus they speak HTTP/2 to it. A workload with a session check\n * gives its clients `SESSION_CHECK`: the knobs of the check, as JSON.\n */\nexport const clientObjects = (cell: KindCell, namespace: string, image: string, target: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"DaemonSet\",\n metadata: { name: CLIENT, namespace, labels: { app: CLIENT } },\n spec: {\n selector: { matchLabels: { app: CLIENT } },\n template: {\n metadata: { labels: { app: CLIENT, [LOG_LABEL]: \"1\" } },\n spec: {\n terminationGracePeriodSeconds: 0,\n tolerations: [{ key: \"node-role.kubernetes.io/control-plane\", operator: \"Exists\", effect: \"NoSchedule\" }],\n // the clients of a zone loss are only in the zone that stays: they see the loss, and they do not have it\n ...(cell.trigger.kind === \"zone-loss\" ? { affinity: keptZone } : {}),\n containers: [\n {\n name: CLIENT,\n image,\n imagePullPolicy: \"Never\",\n command: [\"node\", stackOf(cell) === \"grpc\" ? \"client-grpc.mjs\" : \"client.mjs\"],\n env: [\n { name: \"TARGET\", value: target },\n { name: \"EVERY_MS\", value: String(valueIn(cell.workload.every, \"ms\")) },\n { name: \"TIMEOUT_MS\", value: String(valueIn(cell.workload.timeout, \"ms\")) },\n ...(cell.workload.connections === undefined ? [] : [{ name: \"CONNECTIONS\", value: String(cell.workload.connections) }]),\n ...(cell.declaration?.protocol === \"h2c\" ? [{ name: \"PROTOCOL\", value: \"h2c\" }] : []),\n ...(cell.workload.sessionCheck === undefined ? [] : [{ name: \"SESSION_CHECK\", value: JSON.stringify(cell.workload.sessionCheck) }]),\n { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } },\n { name: \"NODE_NAME\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n },\n ],\n },\n },\n },\n },\n];\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Poll, Stage, Timing } from \"@mark1russell7/service\";\nimport { failSpan, recoverSpan } from \"@mark1russell7/service\";\nimport { bandOf, seconds, secondsOf } from \"./bands.ts\";\nimport type { Band, Declaration, KubeletProbe, Reference, Workload } from \"./cell.ts\";\n\n/**\n * This module is the arithmetic of the predictions. Each band is a range of times after a\n * reference event of the run. Each term comes from a declaration, from the pod settings, or from\n * the calibration of the propagation. No term comes from a default. The arithmetic operates in\n * seconds, and each result is a quantity.\n */\n\nconst s = (amount: Quantity): number => valueIn(amount, \"s\");\n/** This function rounds a time to the microsecond, thus a band has no float residue. */\nconst us = (value: number): number => Math.round(value * 1e6) / 1e6;\nconst band = (lo: number, hi: number): Band => bandOf(us(lo), us(hi));\n\n/**\n * A propagation is the time from a change of an endpoint to the rules of each node. A\n * calibration measures it for each kube-proxy mode and node count. Without a calibration, its upper\n * bound is `minSyncPeriod`: an idle kube-proxy syncs a change at once, or one `minSyncPeriod`\n * after its last sync.\n */\nexport interface Propagation extends Band {\n /** This text tells where the bound comes from: a calibration, or the documented `minSyncPeriod`. */\n readonly from: string;\n}\n\n/** This propagation is the documented bound, before a calibration: from zero to one `minSyncPeriod` of 1 s. */\nexport const DOCUMENTED: Propagation = { lo: seconds(0), hi: seconds(1), from: \"minSyncPeriod (1 s), not calibrated\" };\n\n/**\n * This function gives the time from a flip of the probed state to the decision of the kubelet. The\n * next probe comes within one period, then `threshold − 1` more periods follow. A probe of an\n * endpoint that does not answer fails at its timeout.\n */\nexport const kubeletBand = (probe: KubeletProbe, hangs: boolean): Band => {\n const late = hangs ? s(probe.timeout) : 0;\n return band((probe.threshold - 1) * s(probe.period) + late, probe.threshold * s(probe.period) + late);\n};\n\n/**\n * This function gives the time from a fault of a dependency to the trip of a readiness or liveness\n * latch. A dependency that refuses fails each poll at once. A silent dependency fails each poll at\n * the request timeout of its client, or at the timeout of the poll when that is shorter. The\n * watchdog of the client can close the connection sooner, and then each poll fails at once.\n */\nexport const latchBand = (\n timing: Timing,\n poll: Poll,\n failure: \"refuses\" | \"silent\",\n requestTimeout?: Quantity,\n): Band => {\n const error = failSpan(timing, poll, \"error\");\n if (failure === \"refuses\") return band(s(error.min), s(error.max));\n const wait = requestTimeout === undefined ? s(poll.timeout) : Math.min(s(requestTimeout), s(poll.timeout));\n const late = failSpan(timing, { ...poll, timeout: seconds(wait) }, \"timeout\");\n return band(s(error.min), s(late.max));\n};\n\n/** This function gives the time from a fault to the removal of the endpoints from the rules: the latch, then the kubelet, then the propagation. */\nexport const removalBand = (latch: Band, probe: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(probe, false));\n const trip = secondsOf(latch);\n const spread = secondsOf(propagation);\n return band(trip.lo + kubelet.lo + spread.lo, trip.hi + kubelet.hi + spread.hi);\n};\n\n/**\n * This function gives the time from a fault to the SIGTERM of a replica whose liveness fails with\n * its readiness. The time is the latch, then the liveness probes of the kubelet, then the preStop sleep.\n */\nexport const restartBand = (latch: Band, liveness: KubeletProbe, preStop: Quantity): Band => {\n const kubelet = secondsOf(kubeletBand(liveness, false));\n const trip = secondsOf(latch);\n return band(trip.lo + kubelet.lo + s(preStop), trip.hi + kubelet.hi + s(preStop));\n};\n\n/**\n * This function gives the time from the start of a stall of the event loop to the removal of the\n * replica from the rules. Each probe in the stall fails at its timeout, because the loop does not answer it.\n */\nexport const stallRemovalBand = (readiness: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(readiness, true));\n const spread = secondsOf(propagation);\n return band(kubelet.lo + spread.lo, kubelet.hi + spread.hi);\n};\n\n/**\n * A node lifecycle is the set of timers that removes the pods of a lost node. The same timers give\n * them back after the heal of the node. The lab sets none of them, thus each one is a default of Kubernetes\n * v1.37.0 (file and line in that tag).\n */\nexport interface NodeLifecycle {\n /**\n * This time is `NodeMonitorGracePeriod`. The node lifecycle controller marks a node Unknown when\n * it saw no renewal of its lease for this time. Then it marks each pod of the node not ready.\n */\n readonly grace: Quantity;\n /** This time is `NodeMonitorPeriod`: the controller checks each node once in it. */\n readonly monitor: Quantity;\n /** This time is the renewal interval of the lease of the kubelet. */\n readonly renew: Quantity;\n /** This share is the jitter of each renewal interval: a renewal comes `renew` to `renew × (1 + jitter)` after the one before it. */\n readonly jitter: number;\n /** This time is the margin of the writes of the controller: the status of the node, then each pod of the node. */\n readonly writes: Quantity;\n /** This time is the PING timeout of the HTTP/2 health check of client-go. A kubelet that gets no PING ACK in it connects again. */\n readonly pingTimeout: Quantity;\n /** This time is the longest first backoff of a reflector of client-go before it watches again. */\n readonly rewatch: Quantity;\n /** This time is the sync period of the status manager of the kubelet: it puts back a pod status that the controller changed. */\n readonly statusSync: Quantity;\n}\n\n/**\n * These are the defaults of Kubernetes v1.37.0 for a lost node:\n * - `grace` 50 s and `monitor` 5 s: pkg/controller/nodelifecycle/config/v1alpha1/defaults.go, lines\n * 46 and 52. The controller checks the nodes on a sliding period (node_lifecycle_controller.go,\n * lines 506–510). It sets the probe time when it sees a new lease (lines 952–960), and it marks the\n * node Unknown after `grace` (line 962). Then it marks each pod of the node not ready (lines 768–773).\n * - `renew` 10 s: `NodeLeaseDurationSeconds` 40 (pkg/kubelet/apis/config/v1beta1/defaults.go, line 150)\n * times the fraction 0.25 (pkg/kubelet/kubelet.go, lines 237 and 1156).\n * - `jitter` 0.04: the lease controller renews on a sliding jitter of 0.04\n * (staging/src/k8s.io/component-helpers/apimachinery/lease/controller.go, line 153).\n * - `pingTimeout` 15 s: the PING timeout of the HTTP/2 health check, after a read idle timeout of 30 s\n * (staging/src/k8s.io/apimachinery/pkg/util/net/http.go, lines 131–192).\n * - `rewatch` 1.6 s: a first backoff of 800 ms with a jitter of 1.0\n * (staging/src/k8s.io/client-go/tools/cache/reflector.go, lines 62–67).\n * - `statusSync` 10 s: the sync period of the status manager (pkg/kubelet/status/status_manager.go, line 215).\n * - `writes` 0.25 s is a margin of the lab, not a default.\n */\nexport const NODE_LIFECYCLE: NodeLifecycle = {\n grace: seconds(50),\n monitor: seconds(5),\n renew: seconds(10),\n jitter: 0.04,\n writes: seconds(0.25),\n pingTimeout: seconds(15),\n rewatch: seconds(1.6),\n statusSync: seconds(10),\n};\n\n/**\n * This function gives the time from the loss of a node to the drop of its pods from the rules of\n * the other nodes. The last renewal of the lease comes up to `renew × (1 + jitter)` before the loss.\n * The controller sees it up to one `monitor` later, and marks the node `grace` after that. Then the\n * writes and the propagation follow. The band does not add the low bound of the propagation.\n */\nexport const nodeLossBand = (lifecycle: NodeLifecycle, propagation: Propagation): Band => {\n const last = s(lifecycle.renew) * (1 + lifecycle.jitter);\n return band(s(lifecycle.grace) - last, s(lifecycle.grace) + s(lifecycle.monitor) + s(lifecycle.writes) + s(propagation.hi));\n};\n\n/**\n * This function gives the time from the heal of a lost node until its pods are ready again in the\n * EndpointSlice. The kubelet connects again in one PING timeout at the most: its old connection\n * waits behind the backoff of TCP. Its reflectors watch again after their first backoff. Then its\n * status manager puts back the pod status in one sync period at the most.\n */\nexport const nodeReturnBand = (lifecycle: NodeLifecycle): Band =>\n band(0, s(lifecycle.pingTimeout) + s(lifecycle.rewatch) + s(lifecycle.statusSync) + s(lifecycle.writes));\n\n/** This function gives the cap of a stage in a declaration, or zero when the declaration does not name the stage. */\nexport const capOf = (declaration: Declaration, stage: Stage): Quantity => {\n const found = declaration.phases.find(([name]) => name === stage);\n return seconds(found === undefined ? 0 : s(found[1]));\n};\n\n/**\n * This function gives the shortest time from the deletion of a pod to the stop of its intake. It\n * is the preStop sleep, then the hold of `fail readiness`. The kubelet adds its own delay before\n * the preStop, thus the real time is longer.\n */\nexport const intakeAfter = (declaration: Declaration, preStop: Quantity): Quantity =>\n seconds(us(s(preStop) + s(capOf(declaration, \"fail readiness\"))));\n\n/**\n * A deletion window is what a deletion predicts on one node. The listener of the pod closes at the\n * stop of its intake. The rules of the node stop the new connections to the pod at the\n * propagation. A connection between the two is refused. Thus the window is empty when the intake\n * stops after the latest propagation.\n */\nexport interface DeletionWindow {\n /** This flag tells that no connection can meet a closed listener. */\n readonly empty: boolean;\n /** This band is the end of the window after the deletion: the propagation. */\n readonly end: Band;\n /** This time is the earliest stop of the intake after the deletion. */\n readonly intake: Quantity;\n}\n\n/** This function gives the deletion window of a declaration and a preStop sleep on a node with a propagation. */\nexport const deletionWindow = (declaration: Declaration, preStop: Quantity, propagation: Propagation): DeletionWindow => {\n const intake = intakeAfter(declaration, preStop);\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= spread.hi, end: band(spread.lo, spread.hi), intake };\n};\n\n/**\n * This function gives the window on a node whose kube-proxy pauses across a deletion. The pause\n * starts `lead` before the deletion and holds for `hold`. The node then syncs in one propagation\n * after the end of the pause, at `resume` after the deletion. The window is empty when the intake\n * stops after that.\n */\nexport const pausedWindow = (\n declaration: Declaration,\n preStop: Quantity,\n lead: Quantity,\n hold: Quantity,\n propagation: Propagation,\n): DeletionWindow & { readonly resume: Quantity } => {\n const intake = intakeAfter(declaration, preStop);\n const resume = us(s(hold) - s(lead));\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= resume + spread.hi, end: band(spread.lo, spread.hi), intake, resume: seconds(resume) };\n};\n\n/**\n * A learn window is what `cannot-serve` predicts: when the callers stop getting errors from a\n * replica that cannot serve. The window starts at the fault, and its end comes from `end.from`.\n */\nexport interface LearnWindow {\n /**\n * This time is the interval between two requests on one connection of a client: `every` times the\n * connections. A client of HTTP/2 sends its streams on fewer connections, thus its turn is shorter.\n */\n readonly turn: Quantity;\n /** This band is the trip of the readiness latch of the service after the fault. */\n readonly latch: Band;\n /** This band is the first failed request after the fault: within one turn. */\n readonly start: Band;\n readonly end: { readonly from: Reference; readonly band: Band };\n}\n\n/**\n * This function gives the learn window of a declaration, its readiness probe, a propagation and a\n * workload of keep-alive connections. A caller selects a replica only when it opens a connection:\n * - The lame duck (`lameDuck` true or no value): after the latch, each answer closes its connection,\n * thus each connection of the replica gets one more failure and opens again. The rules select the\n * replica until the removal, thus the last failure comes in one turn after it.\n * - The signal of the lame duck is `Connection: close` on HTTP/1.1, and GOAWAY on HTTP/2. The\n * arithmetic is the same: the turn is `every` times the connections, which are sessions on HTTP/2.\n * - No lame duck: no connection opens again, thus the rules do not matter. The failures end only\n * when the readiness of the service recovers after the heal, and one turn after it.\n */\nexport const learnWindow = (declaration: Declaration, readiness: KubeletProbe, propagation: Propagation, workload: Workload): LearnWindow => {\n const turn = us(s(workload.every) * (workload.connections ?? 1));\n const latch = latchBand(declaration.readiness, declaration.poll, \"refuses\");\n const start = band(0, turn);\n if (declaration.lameDuck === false) {\n const recover = recoverSpan(declaration.readiness, declaration.poll);\n return { turn: seconds(turn), latch, start, end: { from: \"heal\", band: band(0, s(recover.max) + turn) } };\n }\n const removal = secondsOf(removalBand(latch, readiness, propagation));\n return { turn: seconds(turn), latch, start, end: { from: \"fault\", band: band(secondsOf(latch).lo, removal.hi + turn) } };\n};\n\n/**\n * The backlog term of a stall (SYS-42) is in `bands.ts`, thus the judge reads it with no other\n * import. `backlogEnd` adds n × d to the end band. `requestBand` counts the failed requests from\n * the measured period. A window carries the term in `backlog` (`BacklogTerm`).\n */\nexport type { CountBand } from \"./bands.ts\";\nexport { backlogEnd, drainBand, overshootBand, requestBand, showBand } from \"./bands.ts\";\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Finding, Scenario, ServiceDescription, StopWay, Verdict } from \"@mark1russell7/service\";\nimport { lint, lintStop } from \"@mark1russell7/service\";\nimport { redisClient } from \"@mark1russell7/service/redis\";\nimport { DRAIN_RULES } from \"@mark1russell7/service/rules\";\nimport type { KindCell } from \"./cell.ts\";\nimport { compose } from \"./compose.ts\";\nimport type { Propagation } from \"./predict.ts\";\n\n/**\n * The rules of the ordering law that a drain in Kubernetes meets are data in the entry\n * `@mark1russell7/service/rules` (SYS-32). That entry loads no Testcontainers, thus a service can\n * import the rules without kind. This module gives them again, thus the exports of kind stay the\n * same. A kind cell gets the rules that guard the way that its plan stops a pod.\n */\nexport { DRAIN_RULES, O04, O09, O10, O16 } from \"@mark1russell7/service/rules\";\n\nconst seconds = (amount: Quantity): number => valueIn(amount, \"s\");\nconst s = (value: number): Quantity => ({ value, unit: \"s\" });\n\n/**\n * This function gives the scenario of a cell for `lint()`: the preStop sleep, the grace, the probe\n * period and the propagation. The probe windows are the period times the threshold of each probe.\n */\nexport const scenarioOf = (cell: KindCell, propagation: Propagation): Scenario => ({\n platform: {\n \"prestop_sleep@pod\": cell.pod.preStop,\n \"termination_grace@pod\": cell.pod.grace,\n \"probe_period@pod\": cell.pod.readiness.period,\n \"propagation_bound@cluster\": propagation.hi,\n },\n probes: {\n readiness: s(seconds(cell.pod.readiness.period) * cell.pod.readiness.threshold),\n liveness: s(seconds(cell.pod.liveness.period) * cell.pod.liveness.threshold),\n },\n});\n\n/** A verdict set is the verdict of each drain rule for one declaration, by the identifier of the rule. */\nexport type Verdicts = Readonly<Record<string, Verdict>>;\n\n/**\n * This function gives the way that the fault plan of a cell stops a pod, or null when the plan stops no pod.\n * - A deletion, a rolling update, a node drain, a scale-in and a rollout delete a pod with its grace.\n * - In T07 a pause of kube-proxy comes before a deletion. In T18 the pause is on Redis.\n * - The crash of the lab is SIGKILL from the node. Like a crash, it has no preStop hook and no drain.\n * - The other faults are on a link, on Redis, on the DNS of the clients or on the process. The kubelet can restart a pod after them.\n * A zone loss freezes the pods of its node and deletes none of them in its hold.\n */\nexport const stopOf = (cell: KindCell): StopWay | null => {\n switch (cell.trigger.kind) {\n case \"pod-deletion\":\n case \"rolling-update\":\n case \"node-drain\":\n case \"scale-in\":\n case \"rollout\":\n return \"deletion\";\n case \"freeze-process\":\n return cell.template === \"T07\" ? \"deletion\" : null;\n case \"crash\":\n return \"crash\";\n case \"liveness-restart\":\n return \"liveness-restart\";\n case \"rst\":\n case \"blackhole\":\n case \"peer-gone-without-close\":\n case \"app-stall\":\n case \"cpu-throttle\":\n case \"authorization-policy\":\n case \"idle\":\n case \"ztunnel-restart\":\n case \"certificate-expiry\":\n case \"cannot-serve\":\n case \"zone-loss\":\n case \"latency\":\n case \"dns-blackhole\":\n return null;\n }\n};\n\n/**\n * This function applies the drain rules to the description of a cell, and gives each finding. A cell\n * whose plan stops a pod gets the rules of that way (`lintStop()`). A plan that stops no pod gets each\n * drain rule, because a restart by the kubelet or a later deletion can follow its fault.\n */\nexport const lintCell = (description: ServiceDescription, cell: KindCell, propagation: Propagation): Finding[] => {\n const way = stopOf(cell);\n const scenario = scenarioOf(cell, propagation);\n return way === null ? lint(description, scenario, DRAIN_RULES) : lintStop(description, scenario, DRAIN_RULES, way);\n};\n\n/** This function gives the verdict of each drain rule, by its identifier. */\nexport const verdictsOf = (findings: readonly Finding[]): Verdicts =>\n Object.fromEntries(findings.map((finding) => [finding.rule, finding.verdict]));\n\n/** This function gives the description of the service of a cell, as the app of the image composes it. Nothing connects. */\nexport const describeCell = (cell: KindCell): ServiceDescription => {\n if (cell.declaration === null) throw new Error(`${cell.name}: the server is not the service, thus the cell has no declaration to describe`);\n const redis = cell.declaration.redis;\n const client =\n redis === null\n ? undefined\n : redisClient(\"redis\", { host: \"redis\", port: 6379, system: \"systems.component.redis\", knobs: redis.knobs, heartbeatMisses: redis.misses });\n return compose(\"lab\", cell.declaration, { redis: client }).describe();\n};\n","import { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport { imageOf, substrateKey, substrateOf, substrateOn } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Cycle, KindCell, KindRun, Replica } from \"./cell.ts\";\nimport type { CiliumImages } from \"./cilium.ts\";\nimport { CILIUM_IMAGES, CILIUM_VERSION, installCilium } from \"./cilium.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command, createCluster } from \"./cluster.ts\";\nimport type { Step, Target } from \"./faults.ts\";\nimport { NODE, POD, POD_LINK, VERSION, deletes as deletesAPod, dnsCommands, faultSpan, middleboxCommands, placementOf, planOf, sysctlCommand } from \"./faults.ts\";\nimport { labImage } from \"./image.ts\";\nimport type { MeshImages } from \"./istio.ts\";\nimport { EDS_EVERY, ISTIO_IMAGES, LDS_EVERY, MESH_IMAGES, followsZtunnel, installMesh, meshKey, pollsListeners, serverCluster, setZtunnelEnv, ztunnelCertificates } from \"./istio.ts\";\nimport { KUBE_VIP_ANNOUNCER, METALLB_ANNOUNCER, announcerOf, macsAt } from \"./announcer.ts\";\nimport { vipNetworkOf, vipServed } from \"./kubevip.ts\";\nimport { METALLB_NAMESPACE } from \"./metallb.ts\";\nimport type { KindLab, Line, Stamp } from \"./lab.ts\";\nimport { localPin, openKindLab } from \"./lab.ts\";\nimport { OUTSIDE } from \"./names.ts\";\nimport { PRODUCER, PULSAR_IMAGE, PULSAR_NAMESPACE, PULSAR_VERSION, holdPulsar, installPulsar, pulsarObjects, pulsarRecord } from \"./pulsar.ts\";\nimport { CLIENT, REDIS, REDIS_IMAGE, SERVER, clientObjects, namespaceObject, namespaceOf, redisObjects, serverObjects, stackOf } from \"./manifests.ts\";\nimport type { PodLine, Request } from \"./observe.ts\";\nimport { byClients, ztunnelReplaced } from \"./observe.ts\";\nimport type { EnvironmentManifest, KindCellRecord, KindCellResult, LoadSample } from \"./record.ts\";\nimport { FORMAT } from \"./record.ts\";\nimport { scenario } from \"./scenario.ts\";\nimport { judgeKind, observationAfter, observationEnd } from \"./verdict.ts\";\n\n/** This time is the interval of the poll of the rules on each node. */\nexport const RULES_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** These options run a catalog of kind cells. */\nexport interface RunOptions {\n /** This folder gets the raw record of each cell and `summary.ndjson`. */\n readonly out: string;\n readonly log: (line: string) => void;\n /** This count is the number of environments, each a new cluster. */\n readonly environments?: number;\n /** This share of the CPUs of the VM must be idle before a run starts. */\n readonly idle?: number;\n /**\n * This folder holds the run folder of each cluster: its config, its kubeconfig and the cache of\n * kubectl. It is not the output, because a kubeconfig holds credentials.\n */\n readonly work?: string;\n}\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst slug = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n/** This function tells if a cell has Redis: only a declaration of the service can have it. */\nconst hasRedis = (cell: KindCell): boolean => cell.declaration !== null && cell.declaration.redis !== null;\n\n/** These are the images of a cluster: the lab, Redis, grpc-go for the stack `grpc`, and Istio for a mesh. */\ninterface Images {\n readonly lab: string;\n readonly redis: string;\n /** This image is the local image of Pulsar, for the producers of a cell with a broker. */\n readonly pulsar?: string | undefined;\n readonly grpc?: string | undefined;\n readonly mesh?: MeshImages | undefined;\n}\n\n/**\n * This function gives the key of the cluster of a cell: its kube-proxy mode, its node count, and the\n * flavor of its mesh. A zone loss has clusters of its own, with a zone on each node.\n */\nconst clusterKey = (cell: KindCell): string =>\n (cell.mesh !== undefined ? `${cell.mode}-${cell.workers}-${cell.mesh.flavor}` : zoned(cell) ? `${cell.mode}-${cell.workers}-zones` : `${cell.mode}-${cell.workers}`) +\n // a cell with a VIP gets a cluster with its announcer: kube-vip, or MetalLB (\"mlb\", as a kind name has 32 characters at most).\n // Settings of memberlist get a cluster of their own: the probe interval in ms and the multiplier of the suspicion\n (cell.vip === undefined\n ? \"\"\n : cell.vip.announcer !== \"metallb\"\n ? \"-vip\"\n : cell.vip.memberlist === undefined\n ? \"-vip-mlb\"\n : `-mlb${valueIn(cell.vip.memberlist.probeInterval, \"ms\")}x${cell.vip.memberlist.suspicionMult}`) +\n // a cell with a broker gets a cluster with Pulsar\n (cell.pulsar === undefined ? \"\" : \"-pul\");\n\n/** This function gives the name of the cluster of a cell in an environment. Kind permits 32 characters at most: lowercase letters, digits and dashes. */\nexport const clusterNameOf = (cell: KindCell, env: number): string => `lab-${clusterKey(cell)}-${env}`;\n\n/** This function tells if a cell needs a cluster with zones. A zone loss needs one, and so does a cell with settings of memberlist, that shares the cluster of its zone loss. */\nconst zoned = (cell: KindCell): boolean => cell.trigger.kind === \"zone-loss\" || cell.vip?.memberlist !== undefined;\n\n/**\n * This tracker keeps the latest state of the cell from the lines: each pod, each slice and each\n * client. It also has, for each node, the backends of its rules and the hosts of its client sidecar.\n */\nclass Tracker {\n readonly pods = new Map<string, PodLine>();\n readonly slices = new Map<string, Line>();\n readonly backends = new Map<string, readonly string[]>();\n readonly hosts = new Map<string, readonly string[]>();\n readonly clients = new Map<string, { node: string; ok: number; failed: number }>();\n /** This map is the backend of the last answer of each client, by client. */\n readonly answers = new Map<string, string>();\n /** This map is the last send of each producer of a broker that got an answer, and the last that failed. */\n readonly sends = new Map<string, { ok: number; failed: number }>();\n /** This map is the last lookup of the owner of the topic of each producer, and the host that it names. */\n readonly owners = new Map<string, { t: number; owner: string | null }>();\n last = 0;\n /** This time is the last line of a producer. */\n lastSend = 0;\n see = (line: Line): void => {\n if (typeof line.t === \"number\" && line.t > this.last && line.ev === \"req\") this.last = line.t;\n if (line.ev === \"pod\") {\n const pod = line as unknown as PodLine;\n if (pod.type === \"DELETED\") this.pods.delete(pod.name);\n else this.pods.set(pod.name, pod);\n } else if (line.ev === \"slice\") {\n if (line[\"type\"] === \"DELETED\") this.slices.delete(String(line[\"name\"]));\n else this.slices.set(String(line[\"name\"]), line);\n } else if (line.ev === \"rules\") {\n this.backends.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"eds\") {\n this.hosts.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"req\") {\n const request = line as unknown as Request;\n const client = this.clients.get(request.p) ?? { node: request.n, ok: 0, failed: 0 };\n if (request.o === \"ok\") client.ok = Math.max(client.ok, request.t);\n else client.failed = Math.max(client.failed, request.t);\n this.clients.set(request.p, client);\n if (request.o === \"ok\" && request.b !== undefined) this.answers.set(request.p, request.b);\n } else if (line.ev === \"send\") {\n const pod = String(line[\"p\"]);\n const send = this.sends.get(pod) ?? { ok: 0, failed: 0 };\n if (line[\"o\"] === \"ok\") send.ok = Math.max(send.ok, line.t);\n else send.failed = Math.max(send.failed, line.t);\n this.sends.set(pod, send);\n this.lastSend = Math.max(this.lastSend, line.t);\n } else if (line.ev === \"lookup\") {\n const pod = String(line[\"p\"]);\n const owner = line[\"o\"] === \"ok\" && typeof line[\"owner\"] === \"string\" ? line[\"owner\"] : null;\n if (line.t >= (this.owners.get(pod)?.t ?? 0)) this.owners.set(pod, { t: line.t, owner });\n }\n };\n servers(): PodLine[] {\n return [...this.pods.values()].filter((pod) => pod.labels[\"app\"] === SERVER).sort((a, b) => a.name.localeCompare(b.name));\n }\n}\n\n/** A context is the state of one cell in one environment. */\ninterface Context {\n readonly lab: KindLab;\n readonly cluster: Cluster;\n readonly cell: KindCell;\n readonly namespace: string;\n readonly tracker: Tracker;\n readonly clusterIp: string;\n /** This address is the VIP of the cell (`KindCell.vip`), or null for a cell with no VIP. */\n readonly vip: string | null;\n readonly loads: LoadSample[];\n readonly loader: StartedTestContainer;\n readonly idle: number;\n readonly log: (line: string) => void;\n version: number;\n probed: string;\n /** This key is the server pods whose sidecars the LDS pollers read, or \"\" for none. */\n listened: string;\n /** This count is the replicas of the service at rest: one less while a scale-in holds. */\n expected: number;\n /** This map gives the PID of the app of each pod. A replica gets it from the last fault of the target `servers`, and a client from the last gap. */\n readonly pids: Map<string, number>;\n /** This is the speaker of MetalLB of the last SIGSTOP: its node and PID, until its SIGCONT. */\n speaker?: { readonly node: string; readonly pid: number; readonly pod: PodLine | null } | undefined;\n /** This broker is the owner of the topic at the start of the run, for a cell with a broker. It has its pod, its node, and the host that the lookups name. */\n broker: { readonly pod: string; readonly node: string; readonly host: string } | null;\n}\n\n/** This function tells why the cell is not at rest, or gives null when it is. */\nconst unsteady = (ctx: Context): string | null => {\n const { tracker, cell } = ctx;\n const servers = tracker.servers();\n if (servers.length !== ctx.expected) return `${servers.length} replicas, not ${ctx.expected}`;\n const busy = servers.find((pod) => pod.del || !pod.ready);\n if (busy !== undefined) return `${busy.name} is not ready`;\n if (new Set(servers.map((pod) => pod.labels[\"pod-template-hash\"])).size > 1) return \"two versions of the replicas\";\n if (hasRedis(cell)) {\n const redis = [...tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === REDIS);\n if (redis.length !== 1 || !redis[0]!.ready || redis[0]!.del) return \"Redis is not ready\";\n }\n const ips = servers.map((pod) => pod.ip ?? \"\").sort();\n const ready = [...tracker.slices.values()]\n .filter((slice) => slice[\"svc\"] === SERVER)\n .flatMap((slice) => (slice[\"eps\"] as Array<{ ip: string; r: boolean }>).filter((endpoint) => endpoint.r).map((endpoint) => endpoint.ip))\n .sort();\n if (ready.join() !== ips.join()) return `the EndpointSlice has ${ready.join(\" \")}, not ${ips.join(\" \")}`;\n const expected = ips.map((ip) => `${ip}:8080`).sort().join();\n for (const node of ctx.cluster.nodes)\n if ((tracker.backends.get(node) ?? []).join() !== expected) return `the rules of ${node} have ${(tracker.backends.get(node) ?? []).join(\" \")}`;\n // in a sidecar mesh, the client sidecar of each node picks the backend of each call from its endpoints\n if (cell.mesh?.mode === \"sidecar\")\n for (const node of ctx.cluster.nodes)\n if ((tracker.hosts.get(node) ?? []).join() !== expected) return `the client sidecar of ${node} has ${(tracker.hosts.get(node) ?? []).join(\" \") || \"no host\"}`;\n // the clients of a zone loss are only in the zone that stays. The clients of a VIP are outside, on one more \"node\"\n const nodes = new Set([...tracker.clients.values()].map((client) => client.node));\n const clientNodes = ctx.cluster.nodes.length - (cell.trigger.kind === \"zone-loss\" ? 1 : 0) + (cell.vip === undefined ? 0 : 1);\n if (nodes.size !== clientNodes) return `clients answer on ${nodes.size} nodes, not ${clientNodes}`;\n if (cell.vip !== undefined) {\n const outside = [...tracker.clients.values()].filter((client) => client.node === OUTSIDE).length;\n if (outside !== cell.vip.clients) return `${outside} clients outside answer, not ${cell.vip.clients}`;\n }\n for (const [pod, client] of tracker.clients) {\n if (tracker.last - client.ok > 1000) return `client ${pod} has no answer in the last second`;\n if (tracker.last - client.failed < 2000) return `client ${pod} failed in the last 2 s`;\n }\n return null;\n};\n\n/** This function waits until the cell is at rest, and gives null, or the reason when it is not at rest in time. */\nconst steady = async (ctx: Context, within: number): Promise<string | null> => {\n const end = Date.now() + within;\n let why = unsteady(ctx);\n while (why !== null && Date.now() < end) {\n await sleep(200);\n why = unsteady(ctx);\n }\n return why;\n};\n\n/** These taints of the node lifecycle controller mark a node that it does not see as ready. */\nconst NODE_LOSS_TAINTS: readonly string[] = [\"node.kubernetes.io/unreachable\", \"node.kubernetes.io/not-ready\"];\n\n/**\n * This function tells why a node is not at rest for the node lifecycle controller, or gives null. A\n * ready pod is not sufficient after a zone loss. The controller can still see the node as lost, and\n * then it does not mark the pods again at the next loss. The controller removes its taints only after\n * it sees the node ready.\n */\nconst nodeUnsteady = async (cluster: Cluster, name: string): Promise<string | null> => {\n const node = JSON.parse(await cluster.kubectl([\"get\", \"node\", name, \"-o\", \"json\"])) as {\n readonly spec?: { readonly taints?: ReadonlyArray<{ readonly key: string }> };\n readonly status?: { readonly conditions?: ReadonlyArray<{ readonly type: string; readonly status: string }> };\n };\n const ready = node.status?.conditions?.find((condition) => condition.type === \"Ready\")?.status;\n if (ready !== \"True\") return `the node ${name} has the Ready condition ${ready ?? \"none\"}`;\n const taints = (node.spec?.taints ?? []).map((taint) => taint.key).filter((key) => NODE_LOSS_TAINTS.includes(key));\n return taints.length === 0 ? null : `the node ${name} has the taints ${taints.join(\", \")}`;\n};\n\n/** This function waits until a node is at rest for the node lifecycle controller. It gives null, or the reason when the node is not at rest in time. */\nconst nodeSteady = async (cluster: Cluster, name: string, within: number): Promise<string | null> => {\n const end = Date.now() + within;\n let why = await nodeUnsteady(cluster, name);\n while (why !== null && Date.now() < end) {\n await sleep(1000);\n why = await nodeUnsteady(cluster, name);\n }\n return why;\n};\n\n/** This function samples the load of the VM for 4 s, and names the lab containers that are not of this run. */\nconst sample = async (ctx: Context, ours: ReadonlySet<string>): Promise<LoadSample> => {\n const result = await ctx.loader.exec([\"top\", \"-b\", \"-n\", \"2\", \"-d\", \"4\"]);\n const second = result.stdout.split(/^Mem:/m).at(-1) ?? \"\";\n const idle = Number(/(\\d+)% idle/.exec(second)?.[1] ?? \"0\") / 100;\n const top = second\n .split(\"\\n\")\n .filter((line) => /^\\s*\\d+\\s/.test(line))\n .slice(0, 6)\n .map((line) => line.trim().replace(/\\s+/g, \" \"));\n const listed = (await command(\"docker\", [\"ps\", \"--format\", \"{{.ID}} {{.Names}} {{.Image}}\"])).trim().split(\"\\n\");\n const foreign = listed\n .filter((line) => line !== \"\")\n .filter((line) => !ours.has(line.split(\" \")[1]!) && !ours.has(line.split(\" \")[0]!))\n .filter((line) => /loom-|kindest\\/node|toxiproxy|haproxy|redis|systems-kind-lab|f6-/.test(line.split(\" \")[2] ?? \"\"));\n return { t: Date.now(), idle, top, foreign };\n};\n\n/**\n * A run waits for a quiet VM for this time at the most, the bound of the wait for rest. Then the run\n * is void with its reason. In a run of SYS-42, a Cilium cluster at rest stayed at 47% to 59% idle.\n * This is below the share of 60%. That wait had no bound, and it took approximately 30 min.\n */\nconst QUIET_WITHIN = 300_000;\n\n/**\n * This function waits until the VM is quiet: no lab container of another agent, and an idle share of\n * the CPUs. It gives null, or the reason when the VM is not quiet in `within` ms.\n */\nconst quiet = async (ctx: Context, ours: ReadonlySet<string>, within = QUIET_WITHIN): Promise<string | null> => {\n const end = Date.now() + within;\n for (;;) {\n // the prober and the watcher start again with new names, thus the names of this lab come at each sample\n const load = await sample(ctx, new Set([...ours, ...ctx.lab.own(), ctx.loader.getName().replace(/^\\//, \"\")]));\n ctx.loads.push(load);\n if (load.foreign.length === 0 && load.idle >= ctx.idle) return null;\n const why = `${Math.round(load.idle * 100)}% idle, other lab containers: ${load.foreign.join(\"; \") || \"none\"}`;\n if (Date.now() >= end) return `the VM was not quiet in ${within / 1000} s: ${why}`;\n ctx.log(` waiting: ${why}`);\n await sleep(30_000);\n }\n};\n\n/**\n * This function deploys a cell in its namespace, with the watcher, the poll of the rules and the\n * clients. A client of gRPC starts after the server is ready: each failed connect of a channel makes\n * the backoff of grpc-js longer, up to 120 s.\n */\nconst deploy = async (lab: KindLab, cluster: Cluster, cell: KindCell, namespace: string, images: Images, vip: string | null): Promise<string> => {\n await cluster.apply([namespaceObject(cell, namespace)]);\n await lab.watch([namespace], { ztunnel: followsZtunnel(cell) });\n if (hasRedis(cell)) {\n await cluster.apply(redisObjects(namespace, images.redis));\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${REDIS}`, \"--timeout=180s\"]);\n }\n const grpc = stackOf(cell) === \"grpc\";\n if (grpc && images.grpc === undefined) throw new Error(`${cell.name}: the cluster has no image of grpc-go`);\n await cluster.apply(serverObjects(cell, namespace, grpc ? images.grpc! : lab.image, 1, cell.vip === undefined ? null : vip));\n const clusterIp = (await cluster.kubectl([\"-n\", namespace, \"get\", \"service\", SERVER, \"-o\", \"jsonpath={.spec.clusterIP}\"])).trim();\n // a client by name resolves the short name of the Service: the search list of its namespace gives it\n const clients = clientObjects(cell, namespace, lab.image, `${cell.workload.byName === true ? SERVER : clusterIp}:80`);\n if (!grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n if (grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `daemonset/${CLIENT}`, \"--timeout=300s\"]);\n // the producers of a broker, in the image of Pulsar\n if (cell.pulsar !== undefined) {\n if (images.pulsar === undefined) throw new Error(`${cell.name}: the cluster has no image of Pulsar`);\n await cluster.apply(pulsarObjects(cell, namespace, images.pulsar));\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${PRODUCER}`, \"--timeout=300s\"]);\n }\n // the clients of a VIP start outside the cluster after the announcer gave the Service its VIP\n if (cell.vip !== undefined) {\n if (vip === null) throw new Error(`${cell.name}: the cluster has no VIP`);\n const served = await vipServed(cluster, namespace, SERVER, vip);\n if (served !== null) throw new Error(`${cell.name}: ${served}`);\n await lab.outside({ target: `${vip}:80`, every: cell.workload.every, timeout: cell.workload.timeout, count: cell.vip.clients, arp: vip });\n }\n await lab.rules({ mode: cluster.scenario.dataplane, ns: namespace, svc: SERVER, ip: clusterIp, port: 80, every: RULES_EVERY });\n if (cell.mesh?.mode === \"sidecar\") await pollEndpoints(lab, cluster, namespace);\n return clusterIp;\n};\n\n/**\n * This function starts the EDS poll of the client sidecar on each node: the hosts of the server\n * cluster, each 50 ms. The agent enters the network namespace of the client pod through the PID of\n * its client process. The sidecar shares that namespace.\n */\nconst pollEndpoints = async (lab: KindLab, cluster: Cluster, namespace: string): Promise<void> => {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", namespace, \"get\", \"pods\", \"-l\", `app=${CLIENT}`, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string; deletionTimestamp?: string }; spec: { nodeName?: string } }>;\n };\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const item of listed.items.filter((each) => each.metadata.deletionTimestamp === undefined)) {\n const node = item.spec.nodeName ?? \"\";\n pods.push({ node, pod: item.metadata.name, pid: await lab.pidOf(node, namespace, item.metadata.name, CLIENT) });\n }\n await lab.eds({ cluster: serverCluster(namespace), every: EDS_EVERY, pods });\n};\n\n/**\n * This function starts the LDS poll of the sidecar of each server pod, each 50 ms, for a cell that\n * polls them (`pollsListeners`). It starts the pollers again only when the server pods changed. The\n * agent enters the network namespace of the pod through the PID of its app.\n */\nconst pollListeners = async (ctx: Context): Promise<void> => {\n if (!pollsListeners(ctx.cell)) return;\n const servers = ctx.tracker.servers().filter((pod) => !pod.del && pod.node !== null);\n const key = servers.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.listened) return;\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const pod of servers) pods.push({ node: pod.node!, pod: pod.name, pid: await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, \"app\") });\n await ctx.lab.lds({ every: LDS_EVERY, pods });\n ctx.listened = key;\n};\n\n/** This function starts the prober again when the pods of the cell changed: it watches the namespace of each pod. */\nconst probeThePods = async (ctx: Context): Promise<void> => {\n const pods = [...ctx.tracker.pods.values()].filter((pod) => !pod.del && pod.node !== null && pod.cs.every((status) => status.st === \"running\"));\n const key = pods.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.probed) return;\n const netns: number[] = [];\n for (const pod of pods) {\n // a producer of a broker has one container of its name\n const container = pod.labels[\"app\"] === SERVER ? \"app\" : pod.labels[\"app\"] === CLIENT ? CLIENT : pod.labels[\"app\"] === PRODUCER ? PRODUCER : REDIS;\n const pid = await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, container);\n netns.push(await ctx.lab.netnsOf(pod.node!, pid));\n }\n await ctx.lab.probe([...new Set(netns)]);\n ctx.probed = key;\n};\n\n/**\n * This function gives the replicas of the server at this time, in the sequence of their names. The\n * first one is the target of a run. A client of gRPC holds its connection to one replica, and the\n * channels go to the replica that stays after a run. Thus for the stack `grpc`, the first replica\n * is the one that answers the most clients (`byClients`).\n */\nconst replicasOf = (ctx: Context): Replica[] => {\n const replicas = ctx.tracker.servers().map((pod) => ({ pod: pod.name, node: pod.node ?? \"\", ip: pod.ip ?? \"\", version: pod.labels[\"pod-template-hash\"] ?? \"\" }));\n return stackOf(ctx.cell) === \"grpc\" ? byClients(replicas, ctx.tracker.answers) : replicas;\n};\n\n/**\n * This time is how long before the commit of its rules the lab starts the injector in each replica.\n * It is more than the execs of one node before its commit: a read of its tables and the injector.\n */\nconst INJECT_LEAD_MS = 700;\n\n/**\n * This function puts the rules of an action in the network namespace of each replica, with each\n * client pod as a peer. The target `servers` gives all replicas, and `server` the target replica alone.\n * The rules of all replicas commit at one instant on\n * CLOCK_MONOTONIC (`--at`). Thus no client meets one replica with the rule and one without it. The\n * replicas of one node go one after the other, and the nodes at the same time.\n *\n * The fault finds the PID of each replica, and the clear uses it again: thus the heal comes soon\n * after its hold. Before the commit of a clear, the lab reads the filter table of each replica. Its\n * counters show if the rule matched the clients beside the rules of istio-cni. After the commit of a\n * fault, the lab reads the nat table, with the redirect of istio-cni. The stamp is the first commit\n * and the last end.\n */\nconst injectServers = async (ctx: Context, action: string, replicas: readonly Replica[]): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()]\n .filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.ip !== null)\n .map((pod) => pod.ip!)\n .sort();\n if (clients.length === 0 || replicas.length === 0) throw new Error(`the injector needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);\n const clear = action.startsWith(\"clear\");\n const byNode = new Map<string, number[]>();\n for (const replica of replicas) {\n const known = clear ? ctx.pids.get(replica.pod) : undefined;\n const pid = known ?? (await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, \"app\"));\n ctx.pids.set(replica.pod, pid);\n byNode.set(replica.node, [...(byNode.get(replica.node) ?? []), pid]);\n }\n const at = (await ctx.lab.now()) + INJECT_LEAD_MS;\n const args = [action, clients.join(\",\"), \"--at\", String(Math.round(at * 1e6))];\n const stamps = await Promise.all(\n [...byNode].map(async ([node, pids]) => {\n const out: Stamp[] = [];\n for (const pid of pids) {\n if (clear) await ctx.lab.tables(node, pid, \"filter\");\n out.push(await ctx.lab.inject(node, pid, args));\n }\n return out;\n }),\n );\n if (!clear) for (const [node, pids] of byNode) for (const pid of pids) await ctx.lab.tables(node, pid, \"nat\");\n const all = stamps.flat();\n return { t0: Math.min(...all.map((stamp) => stamp.t0)), t1: Math.max(...all.map((stamp) => stamp.t1)) };\n};\n\n/**\n * This function sends a signal to the process of each client pod: the target `clients`. The nodes go\n * at the same time. SIGUSR1 finds the PID of each client, and SIGUSR2 uses it again, thus the heal of\n * a gap comes soon after its hold. When that PID fails, the client started again, and the lab finds\n * its PID. The stamp is the first act and the last end.\n */\nconst signalClients = async (ctx: Context, signal: string): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0) throw new Error(`the signal ${signal} needs clients`);\n const stamps = await Promise.all(\n clients.map(async (pod) => {\n const node = pod.node!;\n const known = signal === \"SIGUSR2\" ? ctx.pids.get(pod.name) : undefined;\n if (known !== undefined) {\n try {\n return await ctx.lab.act(node, { op: \"signal\", pid: known, signal });\n } catch {\n // the client started again: its PID is new\n }\n }\n const pid = await ctx.lab.pidOf(node, ctx.namespace, pod.name, CLIENT);\n ctx.pids.set(pod.name, pid);\n return ctx.lab.act(node, { op: \"signal\", pid, signal });\n }),\n );\n return { t0: Math.min(...stamps.map((stamp) => stamp.t0)), t1: Math.max(...stamps.map((stamp) => stamp.t1)) };\n};\n\n/** This path in the root of a client container holds its placement: the backend of each of its slots, as JSON. */\nconst PLACE_FILE = \"/tmp/lab-place\";\n\n/**\n * This function writes the placement of the sessions of each client pod (`placementOf`). The target\n * is the first replica, and the other slots go to the other replicas. The next SIGHUP of the clients\n * reads it. The nodes go at the same time, and the stamp is the first act and the last end.\n */\nconst placeClients = async (ctx: Context, onTarget: number, replicas: readonly Replica[]): Promise<Stamp> => {\n const connections = ctx.cell.workload.connections;\n if (connections === undefined) throw new Error(\"a placement needs the sessions of each client (connections)\");\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0 || replicas.length === 0) throw new Error(`a placement needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);\n const placement = placementOf(clients.map((pod) => pod.name), connections, onTarget, replicas[0]!.pod, replicas.slice(1).map((replica) => replica.pod));\n const stamps = await Promise.all(\n clients.map(async (pod) => {\n const pid = await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, CLIENT);\n return ctx.lab.act(pod.node!, { op: \"write\", path: `/proc/${pid}/root${PLACE_FILE}`, data: JSON.stringify(placement.get(pod.name)) });\n }),\n );\n return { t0: Math.min(...stamps.map((stamp) => stamp.t0)), t1: Math.max(...stamps.map((stamp) => stamp.t1)) };\n};\n\n/**\n * This function gives the network namespace of each pod of a target, as a node and a PID in it. The\n * pods are each client pod (`clients`), or each replica (`servers`).\n */\nconst namespacesOf = async (ctx: Context, target: Target, replicas: readonly Replica[]): Promise<Array<{ node: string; pod: string; pid: number }>> => {\n if (target === \"servers\") return Promise.all(replicas.map(async (replica) => ({ node: replica.node, pod: replica.pod, pid: await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, \"app\") })));\n if (target !== \"clients\") throw new Error(`a sysctl or a middlebox takes the target clients or servers, not ${target}`);\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0) throw new Error(\"the step needs clients\");\n return Promise.all(clients.map(async (pod) => ({ node: pod.node!, pod: pod.name, pid: await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, CLIENT) })));\n};\n\n/**\n * This function puts a stateful middlebox in the network namespace of each replica, or removes it.\n * Each client pod is a peer (`middleboxCommands`), and the nodes go at the same time. After the set,\n * the lab reads the filter table: it shows the chains of the middlebox. Before the removal, the lab\n * reads the filter table again: the counters show which segments the middlebox dropped.\n */\nconst middleboxServers = async (ctx: Context, idle: Quantity | null, port: number, replicas: readonly Replica[]): Promise<Stamp> => {\n const peers = [...ctx.tracker.pods.values()]\n .filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.ip !== null)\n .map((pod) => pod.ip!)\n .sort();\n const commands = middleboxCommands(peers, idle, port);\n const t0 = await ctx.lab.now();\n const each = await namespacesOf(ctx, \"servers\", replicas);\n await Promise.all(\n each.map(async ({ node, pid }) => {\n if (idle === null) await ctx.lab.tables(node, pid, \"filter\");\n await ctx.lab.netns(node, pid, commands);\n if (idle !== null) await ctx.lab.tables(node, pid, \"filter\");\n }),\n );\n return { t0, t1: await ctx.lab.now() };\n};\n\n/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */\nconst doStep = async (ctx: Context, step: Step, replicas: readonly Replica[], background: Array<Promise<unknown>>): Promise<Stamp | null> => {\n const { lab, cluster, namespace } = ctx;\n const target = replicas[0]!;\n const fill = (args: readonly string[]): string[] =>\n args.map((arg) =>\n arg.replaceAll(\"{namespace}\", namespace).replaceAll(POD, target.pod).replaceAll(NODE, target.node).replaceAll(VERSION, String(ctx.version)),\n );\n const where = async (which: Target): Promise<{ node: string; pid: number; pod: PodLine | null }> => {\n if (which === \"servers\") throw new Error(\"the target servers is a target of the injector only\");\n if (which === \"clients\") throw new Error(\"the target clients is a target of a signal only\");\n if (which === \"server\") return { node: target.node, pid: await lab.pidOf(target.node, namespace, target.pod, \"app\"), pod: null };\n // the speaker of MetalLB on the owner of the VIP: the node of the lost zone, as the hold before the run makes it\n if (which === \"speaker\") {\n const owner = lostNodeOf(cluster);\n const speakers = JSON.parse(\n await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"pods\", \"-l\", \"app.kubernetes.io/component=speaker\", \"--field-selector\", `spec.nodeName=${owner}`, \"-o\", \"json\"]),\n ) as { items: Array<{ metadata: { name: string } }> };\n const pod = speakers.items[0];\n if (pod === undefined) throw new Error(`no speaker of MetalLB is on ${owner}`);\n return { node: owner, pid: await lab.pidOf(owner, METALLB_NAMESPACE, pod.metadata.name, \"speaker\"), pod: null };\n }\n if (which === \"broker\") {\n if (ctx.broker === null) throw new Error(`${ctx.cell.name}: the run has no broker that owns the topic`);\n return { node: ctx.broker.node, pid: await lab.pidOf(ctx.broker.node, PULSAR_NAMESPACE, ctx.broker.pod, \"pulsar-broker\"), pod: null };\n }\n if (which === \"redis\") {\n const redis = [...ctx.tracker.pods.values()].find((pod) => pod.labels[\"app\"] === REDIS && !pod.del)!;\n return { node: redis.node!, pid: await lab.pidOf(redis.node!, namespace, redis.name, REDIS), pod: redis };\n }\n // the target kube-proxy: the kube-proxy of the first worker. The dataplane cilium has no kube-proxy.\n if (cluster.scenario.kubeProxy === null) throw new Error(`${ctx.cell.name}: the target kube-proxy needs kube-proxy, and the dataplane ${cluster.scenario.dataplane} has none`);\n const worker = cluster.nodes[1] ?? cluster.nodes[0]!;\n const proxy = JSON.parse(\n await cluster.kubectl([\"-n\", \"kube-system\", \"get\", \"pods\", \"-l\", \"k8s-app=kube-proxy\", \"--field-selector\", `spec.nodeName=${worker}`, \"-o\", \"json\"]),\n ) as { items: Array<{ metadata: { name: string } }> };\n return { node: worker, pid: await lab.pidOf(worker, \"kube-system\", proxy.items[0]!.metadata.name, \"kube-proxy\"), pod: null };\n };\n switch (step.do) {\n case \"kubectl\": {\n const t0 = await lab.now();\n if (step.args.includes(`LAB_VERSION=${VERSION}`)) ctx.version += 1;\n const call = cluster.kubectl(fill(step.args));\n if (step.background === true) {\n background.push(call.catch((error: unknown) => ctx.log(` kubectl ${step.args.join(\" \")}: ${String(error)}`)));\n return { t0, t1: t0 };\n }\n await call;\n return { t0, t1: await lab.now() };\n }\n case \"signal\": {\n if (step.target === \"clients\") return signalClients(ctx, step.signal);\n // the SIGCONT of a speaker uses the PID of its SIGSTOP: no lookup delays the end of the pause\n const cached = step.target === \"speaker\" && step.signal === \"SIGCONT\" ? ctx.speaker : undefined;\n const at = cached ?? (await where(step.target));\n if (step.target === \"speaker\") ctx.speaker = step.signal === \"SIGSTOP\" ? at : undefined;\n return lab.act(at.node, { op: \"signal\", pid: at.pid, signal: step.signal });\n }\n case \"pause\": {\n const at = await where(step.target);\n const stamp = lab.act(at.node, { op: \"pause\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n background.push(stamp);\n const now = await lab.now();\n return { t0: now, t1: now };\n }\n case \"inject\": {\n if (step.target === \"servers\") return injectServers(ctx, step.action, replicas);\n // the partition of the target: its namespace only, with each client pod as a peer\n if (step.target === \"server\") return injectServers(ctx, step.action, [target]);\n const at = await where(step.target);\n let first: Stamp | null = null;\n for (const replica of replicas) {\n const stamp = await lab.inject(at.node, at.pid, [step.action, replica.ip]);\n first ??= stamp;\n }\n return first;\n }\n case \"flag\": {\n const at = await where(step.target);\n return step.on\n ? lab.act(at.node, { op: \"write\", path: `/proc/${at.pid}/root/tmp/lab-fail`, data: \"1\" })\n : lab.act(at.node, { op: \"remove\", path: `/proc/${at.pid}/root/tmp/lab-fail` });\n }\n case \"cpu\": {\n const at = await where(step.target);\n const cgroup = await lab.cgroupOf(at.node, at.pid);\n return lab.act(at.node, { op: \"write\", path: `${cgroup}/cpu.max`, data: step.max });\n }\n case \"stall\": {\n const at = await where(step.target);\n return lab.act(at.node, { op: \"stall\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n }\n case \"wait\":\n await sleep(valueIn(step.time, \"ms\"));\n return null;\n case \"apply\": {\n // the marks are in the strings of the object: its JSON keeps them as they are\n const object = JSON.parse(fill([JSON.stringify(step.object)])[0]!) as Record<string, unknown>;\n const t0 = await lab.now();\n await cluster.apply([object]);\n return { t0, t1: await lab.now() };\n }\n case \"sysctl\": {\n const command = sysctlCommand(step.key, step.value);\n const t0 = await lab.now();\n await Promise.all((await namespacesOf(ctx, step.target, replicas)).map(({ node, pid }) => lab.netns(node, pid, [command])));\n return { t0, t1: await lab.now() };\n }\n case \"dns\": {\n if (step.target !== \"clients\") throw new Error(`a drop of DNS takes the target clients, not ${step.target}`);\n // the namespaces first, then the rules of all nodes at the same time: the stamp is near the rules\n const each = await namespacesOf(ctx, step.target, replicas);\n const t0 = await lab.now();\n await Promise.all(each.map(({ node, pid }) => lab.netns(node, pid, dnsCommands(step.on))));\n return { t0, t1: await lab.now() };\n }\n case \"certificates\": {\n // a failed read is evidence too: the line has its error, and the run goes on\n const t0 = await lab.now();\n const certificates = await ztunnelCertificates(cluster).catch((error: unknown) => String(error).slice(0, 400));\n const t1 = await lab.now();\n lab.record(typeof certificates === \"string\" ? { ev: \"certificates\", t: t0, t1, error: certificates } : { ev: \"certificates\", t: t0, t1, certificates });\n return { t0, t1 };\n }\n case \"middlebox\":\n if (step.target !== \"servers\") throw new Error(`a middlebox takes the target servers, not ${step.target}`);\n return middleboxServers(ctx, step.idle, step.port, replicas);\n case \"zone\":\n // the agent of the lost node is in its own container, thus the freeze of the node does not stop it\n return lab.act(lostNodeOf(cluster), { op: \"zone\", lost: step.lost });\n case \"place\":\n return placeClients(ctx, step.onTarget, replicas);\n case \"netem\": {\n if (step.target !== \"redis\") throw new Error(`a netem takes the target redis, not ${step.target}`);\n // one qdisc on the link of the Redis pod delays each reply to each replica\n const at = await where(step.target);\n const spec = step.delay === null ? [\"clear-netem\", POD_LINK] : [\"netem\", POD_LINK, \"delay\", `${Math.round(valueIn(step.delay, \"ms\"))}ms`];\n return lab.inject(at.node, at.pid, spec);\n }\n }\n};\n\n/** This function gives the node of the lost zone: the last worker (`zoneOf`). */\nconst lostNodeOf = (cluster: Cluster): string => cluster.nodes.at(-1)!;\n\n/** This function gives the workers of a cluster: each node but the control plane, the first (`nodeNames`). */\nconst workersOf = (cluster: Cluster): string[] => cluster.nodes.slice(1);\n\n/**\n * This function adds a line `vip` to the lines. The line has the state of the announcer, the MAC\n * address of each node on the Docker network, and the name of the announcer. The state of kube-vip\n * is its Lease. The state of MetalLB is the nodes that announce the Service. It also has the wall clock of the runner with its CLOCK_MONOTONIC. The times of the\n * Lease and of the logs are on the wall clock, and each stamp of the lab is on CLOCK_MONOTONIC. The containers of\n * the runner share one kernel, thus the pair converts one into the other.\n */\nconst recordVip = async (ctx: Context): Promise<void> => {\n const wall = Date.now();\n const t = Number(process.hrtime.bigint()) / 1e6;\n const announcer = announcerOf(ctx.cell);\n const state = await announcer.record(ctx.cluster, ctx.namespace, SERVER);\n const { macs } = await vipNetworkOf(ctx.cluster.network);\n ctx.lab.record({ ev: \"vip\", t, wall, vip: ctx.vip, announcer: announcer.name, ...state, macs });\n};\n\n/** This function records the brokers after a run, with the kept lines of each broker. It stamps the wall clock and the monotonic clock. */\nconst recordPulsar = async (ctx: Context): Promise<void> => {\n const wall = Date.now();\n const t = Number(process.hrtime.bigint()) / 1e6;\n ctx.lab.record({ ev: \"pulsar-record\", t, wall, version: PULSAR_VERSION, owner: ctx.broker?.pod ?? null, ...(await pulsarRecord(ctx.cluster)) });\n};\n\n/** This function puts the replicas of a node first, and keeps the sequence of the others. */\nconst lostFirst = (replicas: readonly Replica[], node: string): Replica[] => [\n ...replicas.filter((replica) => replica.node === node),\n ...replicas.filter((replica) => replica.node !== node),\n];\n\n/** This function tells if a run is over: the trigger is done, and the cell is at rest again. */\nconst over = (\n ctx: Context,\n run: { readonly target: Replica | null; readonly replicas: readonly Replica[]; readonly restarts: ReadonlyMap<string, number> },\n lines: readonly Line[],\n): boolean => {\n if (unsteady(ctx) !== null) return false;\n const kind = ctx.cell.trigger.kind;\n if ((kind === \"crash\" || kind === \"liveness-restart\") && run.target !== null) {\n const pod = ctx.tracker.pods.get(run.target.pod);\n const now = pod === undefined ? 0 : pod.cs.reduce((sum, status) => sum + status.rc, 0);\n return now > (run.restarts.get(run.target.pod) ?? 0);\n }\n if (run.target !== null && [\"pod-deletion\", \"node-drain\", \"scale-in\", \"freeze-process\"].includes(kind) && (ctx.cell.template === \"T07\" || ctx.cell.template === \"T15\"))\n return kind === \"scale-in\" || kind === \"node-drain\"\n ? run.replicas.some((replica) => !ctx.tracker.pods.has(replica.pod))\n : !ctx.tracker.pods.has(run.target.pod);\n if (kind === \"rolling-update\" || kind === \"rollout\") return run.replicas.every((replica) => !ctx.tracker.pods.has(replica.pod));\n if (kind === \"app-stall\") return lines.some((line) => line.ev === \"stall\" && line[\"phase\"] === \"end\");\n if (kind === \"ztunnel-restart\") return ztunnelReplaced(lines, ctx.cluster.nodes);\n return true;\n};\n\n/** This function gives the replica that the trigger stopped. For a scale-in or a drain, the controller chooses it. */\nconst stoppedOf = (cell: KindCell, replicas: readonly Replica[], target: Replica | null, lines: readonly Line[]): Replica | null =>\n deletesAPod(cell) ? (replicas.find((replica) => lines.some((line) => line.ev === \"pod\" && line[\"name\"] === replica.pod && line[\"del\"] === true)) ?? target) : target;\n\n/**\n * This function does one run of a cell: at rest, the trigger, the heal, then the observation. The\n * observation lasts until the cell is at rest again, and until each predicted window can close\n * (`observationEnd`).\n */\nconst runOnce = async (ctx: Context, env: number, index: number, warmup: boolean, ours: Set<string>): Promise<KindRun> => {\n const { lab, cell, cluster, namespace } = ctx;\n const plan = planOf(cell);\n const empty = (why: string, start: number): KindRun => ({\n env, index, warmup, start, end: start, acts: { fault: null, heal: null, resume: null }, target: null, paused: null, replicas: [], void: why, lines: lab.take(), dropped: {},\n });\n const atRest = await steady(ctx, 300_000);\n if (atRest !== null) return empty(`the cell was not at rest: ${atRest}`, await lab.now());\n await probeThePods(ctx);\n await pollListeners(ctx);\n const loud = await quiet(ctx, ours);\n if (loud !== null) {\n ctx.log(` ${loud}`);\n return empty(loud, await lab.now());\n }\n const zoneLoss = cell.trigger.kind === \"zone-loss\";\n // the node of a zone loss must be ready for the node lifecycle controller too, with no taint of the last loss\n const lost = zoneLoss ? await nodeSteady(cluster, lostNodeOf(cluster), 300_000) : null;\n if (lost !== null) return empty(`the node of the lost zone was not at rest: ${lost}`, await lab.now());\n // the holder of a VIP is the node of the lost zone at the start of each run, with its announcer\n // ready on each worker. Then the run waits until each client outside sends to the MAC address of the\n // holder: a gratuitous ARP of the holder reached it. The last wait is the settle of the announcer and\n // the timeout. Thus the gratuitous ARPs of the move end before the fault, and each request of the move ends\n if (cell.vip !== undefined) {\n const announcer = announcerOf(cell);\n const holder = lostNodeOf(cluster);\n const held = await announcer.hold(cluster, holder, workersOf(cluster), ctx.namespace, SERVER);\n if (held !== null) return empty(`the VIP is not on ${holder}: ${held}`, await lab.now());\n if (ctx.vip === null) return empty(`${cell.name}: the cluster has no VIP`, await lab.now());\n const mac = (await vipNetworkOf(cluster.network)).macs[holder];\n if (mac === undefined) return empty(`the node ${holder} has no MAC address on the Docker network`, await lab.now());\n const end = Date.now() + valueIn(announcer.settle, \"ms\") + 10_000;\n let moved = macsAt(await lab.outsideMacs(ctx.vip), mac, cell.vip.clients);\n while (moved !== null && Date.now() < end) {\n await sleep(250);\n moved = macsAt(await lab.outsideMacs(ctx.vip), mac, cell.vip.clients);\n }\n if (moved !== null) return empty(`the clients outside do not send to ${holder} (${mac}): ${moved}`, await lab.now());\n await sleep(valueIn(announcer.settle, \"ms\") + valueIn(cell.workload.timeout, \"ms\") + 1000);\n }\n if ((await steady(ctx, 300_000)) !== null) return empty(\"the cell was not at rest after the wait for a quiet VM\", await lab.now());\n // the broker that owns the topic: each broker is ready, each producer sends, and each lookup names that broker\n ctx.broker = null;\n if (cell.pulsar !== undefined) {\n const tracker = ctx.tracker;\n const held = await holdPulsar(cluster, () => ({ owners: tracker.owners, sends: tracker.sends, last: tracker.lastSend }), cell.pulsar.clients);\n if (typeof held === \"string\") return empty(`the producers were not at rest: ${held}`, await lab.now());\n ctx.broker = { pod: held.broker.pod, node: held.broker.node, host: held.host };\n }\n // the target of a zone loss is the replica of the lost node: the zone spread puts one there. With\n // a VIP, no replica is there: the loss takes away the holder of the VIP, and the run has no target\n const replicas = zoneLoss ? lostFirst(replicasOf(ctx), lostNodeOf(cluster)) : replicasOf(ctx);\n if (zoneLoss && cell.vip === undefined && replicas[0]?.node !== lostNodeOf(cluster)) return empty(`no replica is on the node of the lost zone, ${lostNodeOf(cluster)}`, await lab.now());\n const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));\n const target =\n cell.vip !== undefined || cell.pulsar !== undefined || (hasRedis(cell) && [\"rst\", \"blackhole\", \"freeze-process\", \"peer-gone-without-close\", \"rollout\", \"latency\"].includes(cell.trigger.kind)) ? null : replicas[0]!;\n // the paused node is the node of kube-proxy that a freeze stops, or the node of the lost zone\n const paused = cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\" && cell.vip === undefined ? (cluster.nodes[1] ?? cluster.nodes[0]!) : zoneLoss ? lostNodeOf(cluster) : null;\n // A SYN with no answer before the run keeps its NAT for 120 s (SYN_SENT). A client that takes its\n // port again in the run meets it, and the restore of the last run leaves such SYNs. Each run starts with none\n const cleared = await lab.forgetHandshakes(ctx.clusterIp, 80);\n lab.take();\n for (const key of Object.keys(lab.dropped)) delete lab.dropped[key];\n const start = await lab.now();\n const background: Array<Promise<unknown>> = [];\n let fault: number | null = null;\n let resume: number | null = null;\n if (cell.trigger.kind === \"scale-in\") ctx.expected = cell.pod.replicas - 1;\n const notes: string[] = [];\n const forgotten = Object.entries(cleared).filter(([, count]) => count > 0);\n if (forgotten.length > 0) notes.push(`before the run, the lab deleted SYN_SENT entries of the ClusterIP: ${forgotten.map(([node, count]) => `${count} on ${node}`).join(\", \")}`);\n // the steps before the fault. A failed one makes the run void with its reason, after the restore\n for (const step of plan.prepare ?? []) {\n try {\n await doStep(ctx, step, replicas, background);\n } catch (error) {\n const why = `the step ${step.do} before the fault failed: ${String(error).slice(0, 400)}`;\n ctx.log(` ${why}`);\n for (const each of plan.restore) await doStep(ctx, each, replicas, background).catch((failure: unknown) => ctx.log(` the restore ${each.do} failed: ${String(failure)}`));\n return empty(why, start);\n }\n }\n for (const step of plan.inject) {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && fault === null) fault = stamp.t0;\n }\n let heal: number | null = null;\n // the heal of one fault, `hold` after its stamp: the stamp of its first act, or null\n const healAfter = async (from: number | null, hold: Quantity): Promise<number | null> => {\n const now = await lab.now();\n await sleep(Math.max(0, valueIn(hold, \"ms\") - (now - (from ?? now))));\n let stamped: number | null = null;\n for (const step of plan.heal) {\n try {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && stamped === null) stamped = stamp.t0;\n } catch (error) {\n notes.push(`the heal ${step.do} failed: ${String(error)}`);\n }\n }\n return stamped;\n };\n const cycles: Cycle[] = [];\n if (plan.hold !== null) {\n heal = await healAfter(fault, plan.hold);\n if (fault !== null && heal !== null) cycles.push({ fault, heal });\n // each flap: the rest from the heal before it, the inject again, and its heal after its own hold\n for (const flap of plan.flaps ?? []) {\n const now = await lab.now();\n await sleep(Math.max(0, valueIn(flap.rest, \"ms\") - (now - (heal ?? now))));\n let again: number | null = null;\n for (const step of plan.inject) {\n try {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && again === null) again = stamp.t0;\n } catch (error) {\n notes.push(`the inject ${step.do} of a flap failed: ${String(error)}`);\n }\n }\n const healed = await healAfter(again, flap.hold);\n if (again !== null && healed !== null) cycles.push({ fault: again, heal: healed });\n heal = healed ?? heal;\n }\n }\n const flapped = (plan.flaps ?? []).length > 0;\n if (flapped && cycles.length !== (plan.flaps ?? []).length + 1) notes.push(`the run has ${cycles.length} cycles with stamps of the ${(plan.flaps ?? []).length + 1} of the trigger`);\n const flapping = flapped ? { cycles } : {};\n const settled = await Promise.allSettled(background);\n for (const each of settled) {\n if (each.status === \"fulfilled\" && typeof each.value === \"object\" && each.value !== null && \"t1\" in each.value) resume = (each.value as Stamp).t1;\n }\n // the observation: at least 3 s after the last act, until the trigger happened and the cell is at rest again\n const limit = Date.now() + 240_000 + valueIn(cell.pod.grace, \"ms\") + valueIn(cell.pod.preStop, \"ms\");\n await sleep(3000);\n while (Date.now() < limit && !over(ctx, { target, replicas, restarts }, lab.lines)) await sleep(250);\n // then until each predicted window can close and each request that started in it ended (`observationEnd`)\n const until = observationEnd(cell, { start, acts: { fault, heal, resume, ...flapping }, target: stoppedOf(cell, replicas, target, lab.lines), replicas, lines: lab.lines });\n if (until !== null) {\n const wait = Math.min(until - (await lab.now()), limit - Date.now());\n if (wait > 0) await sleep(wait);\n }\n const end = await lab.now();\n const timedOut = Date.now() >= limit;\n // the Lease of a VIP after the run gives the time that the new holder took it\n if (cell.vip !== undefined) await recordVip(ctx).catch((error: unknown) => notes.push(`the record of the VIP failed: ${String(error)}`));\n if (cell.pulsar !== undefined) await recordPulsar(ctx).catch((error: unknown) => notes.push(`the record of Pulsar failed: ${String(error)}`));\n for (const step of plan.restore) {\n try {\n await doStep(ctx, step, replicas, background);\n } catch (error) {\n notes.push(`the restore ${step.do} failed: ${String(error)}`);\n }\n }\n ctx.expected = cell.pod.replicas;\n for (const note of notes) ctx.log(` ${note}`);\n const lines = lab.take();\n const stopped = stoppedOf(cell, replicas, target, lines);\n const restarted = lines.some((line) => line.ev === \"pod\" && (line[\"cs\"] as Array<{ rc: number }>).some((status) => status.rc > 0));\n if (restarted) {\n // a container that restarted gets a back-off on its next restart: new pods start without one\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"restart\", `deployment/${SERVER}`]);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n }\n if (cell.trigger.kind === \"node-drain\") await rebalance(ctx);\n ctx.log(` ${warmup ? \"warm-up\" : `run ${index}`}: ${sec(end - start)} s${timedOut ? \", not at rest in time\" : \"\"}`);\n return {\n env, index, warmup, start, end, acts: { fault, heal, resume, ...flapping }, target: stopped, paused, ...(ctx.broker === null ? {} : { broker: ctx.broker.host }), replicas, void: null, lines, dropped: { ...lab.dropped }, notes,\n };\n};\n\nconst sec = (ms: number): number => Math.round(ms) / 1000;\n\n/** This function moves a replica back to a node without one after a drain: it deletes one replica of the node with two. */\nconst rebalance = async (ctx: Context): Promise<void> => {\n await steady(ctx, 300_000);\n const servers = ctx.tracker.servers();\n const byNode = new Map<string, string[]>();\n for (const pod of servers) byNode.set(pod.node ?? \"\", [...(byNode.get(pod.node ?? \"\") ?? []), pod.name]);\n const crowded = [...byNode.values()].find((pods) => pods.length > 1);\n if (crowded !== undefined && byNode.size < ctx.cluster.nodes.length - 1)\n await ctx.cluster.kubectl([\"-n\", ctx.namespace, \"delete\", \"pod\", crowded[0]!, \"--wait=true\"]);\n};\n\n/** This function writes the result of a cell: its raw record, and one line of its verdict in `summary.ndjson`. */\nexport const writeResult = (out: string, result: KindCellResult): void => {\n mkdirSync(out, { recursive: true });\n const record: KindCellRecord = { format: FORMAT, ...result };\n writeFileSync(join(out, `${slug(result.cell.name)}.json`), JSON.stringify(record));\n const { verdict, cell } = result;\n writeFileSync(\n join(out, \"summary.ndjson\"),\n `${JSON.stringify({\n id: cell.id,\n name: cell.name,\n mode: cell.mode,\n trigger: cell.trigger.kind,\n prediction: cell.prediction.kind,\n decision: verdict.decision,\n counted: verdict.counted,\n voided: verdict.voided,\n failed: verdict.failed,\n median: verdict.median,\n why: verdict.why,\n substrate: substrateKey(substrateOf(result.manifests[0] ?? {}, result.at, \"E2\")),\n })}\\n`,\n { flag: \"a\" },\n );\n};\n\n/**\n * This function does the runs of a catalog of kind cells. The cells of one kube-proxy mode and node\n * count share a cluster. Each group gets new clusters in sequence, one for each environment. Each\n * environment does its share of the runs of each cell, after one warm-up. The function deletes each\n * cluster after its cells.\n */\nexport const runKindCells = async (cells: readonly KindCell[], options: RunOptions): Promise<KindCellResult[]> => {\n const environments = options.environments ?? 2;\n const image = await labImage();\n const redisImage = await localPin(REDIS_IMAGE);\n // the server of the stack grpc: the image of grpc-go of the harness, built from its tag\n const grpcImage = cells.some((cell) => stackOf(cell) === \"grpc\") ? await imageOf({ kind: \"shipped\", name: \"grpc-go\" }) : undefined;\n // Istio: each image of each flavor of the cells pulled once by its digest, and tagged with it, for `kind load`\n const flavors = new Set(cells.flatMap((cell) => (cell.mesh === undefined ? [] : [cell.mesh.flavor])));\n const pinned = new Map<keyof typeof ISTIO_IMAGES, string>();\n for (const name of new Set([...flavors].flatMap((flavor) => MESH_IMAGES[flavor]))) pinned.set(name, await localPin(ISTIO_IMAGES[name]));\n const meshImages: MeshImages | undefined =\n flavors.size === 0 ? undefined : { pilot: pinned.get(\"pilot\")!, proxy: pinned.get(\"proxy\")!, cni: pinned.get(\"cni\"), ztunnel: pinned.get(\"ztunnel\") };\n // Cilium: each image pulled once by its digest, if a cell has the dataplane cilium\n const cilium: CiliumImages | undefined = cells.some((cell) => cell.mode === \"cilium\")\n ? { agent: await localPin(CILIUM_IMAGES.agent), operator: await localPin(CILIUM_IMAGES.operator), envoy: await localPin(CILIUM_IMAGES.envoy) }\n : undefined;\n // the announcers of the VIPs: each image pulled once by its digest, if a cell has a VIP with that announcer\n const announcerImages = new Map<string, Record<string, string>>();\n for (const announcer of [KUBE_VIP_ANNOUNCER, METALLB_ANNOUNCER]) {\n if (!cells.some((cell) => cell.vip !== undefined && announcerOf(cell).name === announcer.name)) continue;\n const pinned: Record<string, string> = {};\n for (const [name, ref] of Object.entries(announcer.images)) pinned[name] = await localPin(ref);\n announcerImages.set(announcer.name, pinned);\n }\n // Pulsar: its image pulled once by its digest, if a cell has a broker\n const pulsarImage = cells.some((cell) => cell.pulsar !== undefined) ? await localPin(PULSAR_IMAGE) : undefined;\n const images: Images = { lab: image, redis: redisImage, grpc: grpcImage, mesh: meshImages, pulsar: pulsarImage };\n // the cells of one kube-proxy mode, node count and mesh flavor share a cluster\n const groups = new Map<string, KindCell[]>();\n for (const cell of cells) groups.set(clusterKey(cell), [...(groups.get(clusterKey(cell)) ?? []), cell]);\n const results: KindCellResult[] = [];\n for (const group of groups.values()) {\n const partial = new Map<string, { runs: KindRun[]; manifests: EnvironmentManifest[]; loads: LoadSample[] }>();\n for (let env = 0; env < environments; env += 1) {\n const s = scenario(clusterNameOf(group[0]!, env), group[0]!.mode, group[0]!.workers, undefined, zoned(group[0]!));\n const dir = join(options.work ?? join(tmpdir(), \"systems-kind-work\"), s.name);\n const mesh = group[0]!.mesh;\n options.log(`cluster ${s.name}: ${s.dataplane}, ${s.workers} workers${mesh === undefined ? \"\" : `, Istio ${mesh.flavor}`}`);\n const cluster = await createCluster(s, dir);\n let lab: KindLab | null = null;\n let loader: StartedTestContainer | null = null;\n try {\n // the CNI first: with no CNI, no pod of the lab can start\n if (s.cni === \"cilium\") {\n if (cilium === undefined) throw new Error(`${group[0]!.name}: the run has no images of Cilium`);\n for (const each of [cilium.agent, cilium.operator, cilium.envoy]) await cluster.load(each);\n const began = Date.now();\n await installCilium(cluster, cilium);\n options.log(` Cilium ${CILIUM_VERSION} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n await cluster.load(image);\n await cluster.load(redisImage);\n if (grpcImage !== undefined && group.some((cell) => stackOf(cell) === \"grpc\")) await cluster.load(grpcImage);\n if (mesh !== undefined) {\n if (images.mesh === undefined) throw new Error(`${group[0]!.name}: the run has no images of Istio`);\n for (const name of MESH_IMAGES[mesh.flavor]) {\n const each = images.mesh[name];\n if (each === undefined) throw new Error(`${group[0]!.name}: the run has no image ${name} of Istio`);\n await cluster.load(each);\n }\n const began = Date.now();\n await installMesh(cluster, mesh.flavor, images.mesh);\n options.log(` Istio ${mesh.flavor} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n // the announcer on each worker, and the VIP on the node of the lost zone: its address near the end of the Docker network\n let vip: string | null = null;\n if (group[0]!.vip !== undefined) {\n const announcer = announcerOf(group[0]!);\n const pinned = announcerImages.get(announcer.name);\n if (pinned === undefined) throw new Error(`${group[0]!.name}: the run has no images of ${announcer.name}`);\n for (const each of Object.values(pinned)) await cluster.load(each);\n const began = Date.now();\n vip = announcer.vipOf((await vipNetworkOf(cluster.network)).subnet, lostNodeOf(cluster), workersOf(cluster));\n await announcer.install(cluster, pinned, lostNodeOf(cluster), workersOf(cluster), vip, group[0]!.vip);\n options.log(` ${announcer.name} installed in ${Math.round((Date.now() - began) / 100) / 10} s, the VIP ${vip}`);\n }\n // Pulsar: one ZooKeeper, one bookie and two brokers, for the cells with a broker\n if (group[0]!.pulsar !== undefined) {\n if (pulsarImage === undefined) throw new Error(`${group[0]!.name}: the run has no image of Pulsar`);\n await cluster.load(pulsarImage);\n const began = Date.now();\n await installPulsar(cluster, pulsarImage);\n options.log(` Pulsar ${PULSAR_VERSION} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n lab = await openKindLab(cluster, image);\n const { GenericContainer } = await import(\"testcontainers\");\n const Shared = class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n loader = await new Shared(image).withPidMode(\"host\").withNetworkMode(\"none\").withCommand([\"sleep\", \"infinity\"]).start();\n const manifest = await cluster.manifest();\n const ours = new Set<string>([...cluster.nodes]);\n for (const cell of group) {\n const namespace = namespaceOf(cell);\n options.log(`${cell.name} (${cell.id}), environment ${env}`);\n const tracker = new Tracker();\n const off = lab.listen(tracker.see);\n const loads: LoadSample[] = [];\n // the environment of ztunnel for the cell, before its pods: thus its rollout ends no flow of the cell\n const ztunnelEnv = cell.mesh?.ztunnel;\n try {\n if (ztunnelEnv !== undefined) await setZtunnelEnv(cluster, ztunnelEnv, false);\n const clusterIp = await deploy(lab, cluster, cell, namespace, images, vip);\n for (const name of [...lab.own(), loader.getName().replace(/^\\//, \"\")]) ours.add(name);\n const ctx: Context = { lab, cluster, cell, namespace, tracker, clusterIp, vip: cell.vip === undefined ? null : vip, loads, loader, idle: options.idle ?? 0.6, log: options.log, version: 1, probed: \"\", listened: \"\", expected: cell.pod.replicas, pids: new Map(), broker: null };\n const share = Math.ceil(cell.runs / environments) - (env < cell.runs % environments || cell.runs % environments === 0 ? 0 : 1);\n const runs: KindRun[] = [await runOnce(ctx, env, -1, true, ours)];\n for (let index = 0; index < share; index += 1) runs.push(await runOnce(ctx, env, index, false, ours));\n const entry = partial.get(cell.name) ?? { runs: [], manifests: [], loads: [] };\n entry.runs.push(...runs);\n entry.manifests.push({\n env,\n scenario: s,\n cluster: manifest,\n substrate: substrateOn(\"E2\"),\n images: {\n lab: image,\n redis: redisImage,\n ...(stackOf(cell) === \"grpc\" ? { grpc: grpcImage! } : {}),\n ...(cell.mesh === undefined ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]]))),\n ...(s.cni === \"cilium\" ? Object.fromEntries(Object.entries(CILIUM_IMAGES).map(([name, ref]) => [`cilium-${name}`, ref])) : {}),\n ...(cell.vip === undefined ? {} : announcerOf(cell).images),\n ...(cell.pulsar === undefined ? {} : { pulsar: PULSAR_IMAGE }),\n },\n });\n entry.loads.push(...loads);\n partial.set(cell.name, entry);\n mkdirSync(join(options.out, \"partial\"), { recursive: true });\n writeFileSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), JSON.stringify({ cell, runs, manifest, loads }));\n } finally {\n off();\n await lab.outside(null).catch(() => undefined);\n await lab.rules(null).catch(() => undefined);\n await lab.eds(null).catch(() => undefined);\n await lab.lds(null).catch(() => undefined);\n await lab.stopProbe();\n await cluster.kubectl([\"delete\", \"namespace\", namespace, \"--wait=true\", \"--timeout=300s\"]).catch((error: unknown) => options.log(` ${String(error)}`));\n if (ztunnelEnv !== undefined) await setZtunnelEnv(cluster, ztunnelEnv, true).catch((error: unknown) => options.log(` the undo of the environment of ztunnel failed: ${String(error)}`));\n }\n }\n } finally {\n if (loader !== null) await loader.stop({ timeout: 0 }).catch(() => undefined);\n if (lab !== null) await lab.close();\n await cluster.delete();\n options.log(`cluster ${s.name} deleted`);\n }\n }\n for (const cell of group) {\n const entry = partial.get(cell.name);\n if (entry === undefined) continue;\n const result: KindCellResult = { cell, ...entry, verdict: judgeKind(cell, entry.runs), at: new Date().toISOString() };\n writeResult(options.out, result);\n rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env0.json`), { force: true });\n for (let env = 1; env < environments; env += 1) rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), { force: true });\n options.log(`${cell.name}: ${result.verdict.decision}, ${result.verdict.why}`);\n results.push(result);\n }\n }\n return results;\n};\n\n/** This function gives the estimate of a cell in seconds, for the arithmetic of the shards. */\nconst estimate = (cell: KindCell, environments = 2): number => {\n // the span of the faults: the hold, then the rest and the hold of each flap\n const hold = valueIn(faultSpan(cell.trigger), \"s\");\n const trigger: Record<string, number> = {\n \"liveness-restart\": 100,\n crash: 30,\n \"app-stall\": hold + 40,\n \"cpu-throttle\": hold + 40,\n rollout: hold + 50,\n \"authorization-policy\": hold + 20,\n idle: hold + 5,\n // the grace of 30 s of the chart of ztunnel, then the removal of the old pods\n \"ztunnel-restart\": 40,\n // the hold, and a read of the certificates of each ztunnel pod at the fault and at the heal\n \"certificate-expiry\": hold + 20,\n \"cannot-serve\": hold + 20,\n // the hold, then the kubelet of the lost node connects again and the replica is ready again\n \"zone-loss\": hold + 45,\n // the pauses of a speaker (SYS-57): the schedule, then a rejoin of memberlist\n ...(cell.vip === undefined ? {} : { \"freeze-process\": hold + 20 }),\n };\n // a window can need a longer observation: from the end of the hold, until each request that started in it ended\n const after = observationAfter(cell);\n const observed = after === null ? 0 : hold + valueIn(after, \"s\");\n // the band of `ends` too: until each socket can end\n const closes = cell.prediction.kind === \"ends\" && cell.prediction.band !== null ? valueIn(cell.prediction.band.hi, \"s\") + 3 : 0;\n // a quiet black hole: the stop of the calls before the fault, the hold, and the start of the calls after the clear\n const quiet = cell.trigger.quiet === true ? hold + 10 : 0;\n // a VIP moves back to the node of the lost zone before each run: a release or a rejoin, and a gratuitous ARP\n const move = cell.vip === undefined ? 0 : 30;\n // a broker: the hold waits for the broker of the last run, and the producers come back in up to approximately 90 s\n const broker = cell.pulsar === undefined ? 0 : 150;\n const each = 25 + move + broker + Math.max(observed, closes, quiet, trigger[cell.trigger.kind] ?? (cell.template === \"T18\" ? hold + 35 : 15 + valueIn(cell.pod.preStop, \"s\")));\n // the install of Istio in each cluster takes 1 to 2 min, and the install of an announcer approximately 1 min\n // and the install of Pulsar approximately 4 min, with the load of its image\n const setup = (cell.mesh === undefined ? 60 : 180) + (cell.vip === undefined ? 0 : 60) + (cell.pulsar === undefined ? 0 : 240);\n return setup * environments + (cell.runs + environments) * each;\n};\n\n/**\n * This function gives an estimate of the time of a cell: its setup, then each run and each\n * warm-up. A run waits for the cell at rest, samples the load, does the trigger and waits until the\n * cell is at rest again.\n */\nexport const estimatedTime = (cell: KindCell, environments = 2): Quantity => ({ value: estimate(cell, environments), unit: \"s\" });\n\n/**\n * This function gives the share of a shard of a catalog: cells in sequence. Thus the cells of one\n * mode and one mesh flavor stay together and share clusters. Each shard gets an equal share of the\n * estimated time. In one mode, the cells with no mesh come first, in the sequence of the catalog.\n */\nexport const shardOf = <T extends KindCell>(cells: readonly T[], index: number, count: number): T[] => {\n if (!(Number.isInteger(index) && Number.isInteger(count) && count >= 1 && index >= 0 && index < count))\n throw new RangeError(`shard ${index} of ${count} is not a shard`);\n const rank = (cell: KindCell): string => `${cell.mode}/${meshKey(cell) === \"none\" ? \"\" : meshKey(cell)}`;\n const ordered = [...cells].sort((a, b) => (rank(a) === rank(b) ? 0 : rank(a) < rank(b) ? -1 : 1));\n const total = ordered.reduce((sum, cell) => sum + estimate(cell), 0);\n const out: T[] = [];\n let before = 0;\n for (const cell of ordered) {\n const middle = before + estimate(cell) / 2;\n if (Math.min(count - 1, Math.floor((middle / total) * count)) === index) out.push(cell);\n before += estimate(cell);\n }\n return out;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["whole","sleep","until","goDuration","sleep","KEPT","sleep","s","seconds","DRAIN_RULES","deletesAPod"],"sources":["../src/backends.ts","../src/scenario.ts","../src/tools.ts","../src/cluster.ts","../src/helm.ts","../src/cilium.ts","../src/compose.ts","../src/eds.ts","../src/faults.ts","../src/image.ts","../src/istio.ts","../src/kubevip.ts","../src/metallb.ts","../src/announcer.ts","../src/pulsar.ts","../src/lds.ts","../src/conntrack.ts","../src/lab.ts","../src/manifests.ts","../src/predict.ts","../src/quiet.ts","../src/rules.ts","../src/run.ts"],"sourcesContent":["// The parser of the rules of kube-proxy and of the services of Cilium. The agent of each node reads\n// the rules with it, and the image copies this file, thus it imports nothing.\n\n/** A service rule names the Service whose backends the agent reads: its namespace, its name, and its ClusterIP and port for IPVS and Cilium. */\nexport interface ServiceRule {\n readonly mode: \"iptables\" | \"ipvs\" | \"nftables\" | \"cilium\";\n readonly ns: string;\n readonly svc: string;\n readonly ip: string;\n readonly port: number;\n}\n\nconst hexIp = (hex: string): string =>\n [0, 2, 4, 6].map((at) => parseInt(hex.slice(at, at + 2), 16)).join(\".\");\n\n/**\n * This function gives the backends of a Service in a dump of the rules of kube-proxy, as\n * `ip:port`, sorted. A backend of IPVS with the weight zero takes no new connection, thus it is\n * not a backend.\n * - iptables: the `KUBE-SVC` chain of the Service jumps to a `KUBE-SEP` chain for each backend, and that chain does the DNAT.\n * - nftables: the `service-` chain of the Service does the DNAT to each backend, or it names an `endpoint-` chain for each one.\n * - IPVS: `/proc/net/ip_vs` has the line of the virtual service, then a line for each real server.\n * - Cilium: the JSON of the services of the agent (`ciliumBackendsOf`).\n */\nexport const backendsOf = (mode: ServiceRule[\"mode\"], text: string, rule: Pick<ServiceRule, \"ns\" | \"svc\" | \"ip\" | \"port\">): string[] => {\n if (mode === \"cilium\") return ciliumBackendsOf(text, rule);\n const out = new Set<string>();\n const lines = text.split(\"\\n\");\n if (mode === \"iptables\") {\n const seps = new Set<string>();\n for (const line of lines) {\n if (!line.startsWith(\"-A KUBE-SVC-\") || !line.includes(`${rule.ns}/${rule.svc}:`)) continue;\n const jump = / -j (KUBE-SEP-\\S+)/.exec(line);\n if (jump !== null) seps.add(jump[1]!);\n }\n for (const line of lines) {\n const chain = /^-A (KUBE-SEP-\\S+) /.exec(line);\n if (chain === null || !seps.has(chain[1]!)) continue;\n const to = / --to-destination (\\S+)/.exec(line);\n if (to !== null) out.add(to[1]!);\n }\n } else if (mode === \"nftables\") {\n // a chain name with a slash can print in quotes: `chain \"service-…\" {` and `goto \"endpoint-…\"`\n let inside = false;\n const ours = `-${rule.ns}/${rule.svc}/`;\n for (const line of lines) {\n const head = /^\\s*chain\\s+\"?([^\"\\s{]+)\"?\\s*\\{/.exec(line);\n if (head !== null) inside = head[1]!.startsWith(\"service-\") && head[1]!.includes(ours);\n else if (/^\\s*(set|map)\\s/.test(line)) inside = false;\n if (!inside) continue;\n // one chain for each endpoint, as kube-proxy 1.31 writes it\n for (const match of line.matchAll(/(?:goto|jump)\\s+\"?endpoint-[0-9A-Za-z]+(-[^\\s,}\"]+)__([0-9.]+)\\/(\\d+)\"?/g))\n if (match[1]!.startsWith(ours)) out.add(`${match[2]}:${match[3]}`);\n // a DNAT in the service chain, as kube-proxy 1.37 writes it: `numgen random mod 2 map { 0 : IP . PORT, … }`, or `dnat to IP:PORT`\n for (const match of line.matchAll(/(?:^|[\\s{,:])(\\d+\\.\\d+\\.\\d+\\.\\d+) \\. (\\d+)(?=\\s*[,}]|\\s*$)/g)) out.add(`${match[1]}:${match[2]}`);\n for (const match of line.matchAll(/\\bdnat (?:ip )?to (\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)/g)) out.add(`${match[1]}:${match[2]}`);\n }\n } else {\n let inside = false;\n for (const line of lines) {\n // each virtual service starts a block: a UDP or an SCTP one ends the block of the TCP service before it\n const service = /^(TCP|UDP|SCTP)\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s/.exec(line);\n if (service !== null) {\n inside = service[1] === \"TCP\" && hexIp(service[2]!) === rule.ip && parseInt(service[3]!, 16) === rule.port;\n continue;\n }\n const real = /^\\s+->\\s+([0-9A-F]{8}):([0-9A-F]{4})\\s+\\S+\\s+(\\d+)/.exec(line);\n if (inside && real !== null && Number(real[3]) > 0) out.add(`${hexIp(real[1]!)}:${parseInt(real[2]!, 16)}`);\n }\n }\n return [...out].sort();\n};\n\n/** This is one service of the agent of Cilium, as `GET /v1/service` gives it: the fields that the parser reads. */\ninterface CiliumService {\n readonly spec?: {\n readonly \"frontend-address\"?: { readonly ip?: string; readonly port?: number; readonly protocol?: string; readonly scope?: string };\n readonly \"backend-addresses\"?: ReadonlyArray<{ readonly ip?: string; readonly port?: number; readonly state?: string }> | null;\n readonly flags?: { readonly type?: string; readonly name?: string; readonly namespace?: string };\n };\n}\n\n/** This function gives the services in a reply of the agent of Cilium. It refuses a reply that is not a JSON list. */\nconst ciliumServices = (text: string): CiliumService[] => {\n const data: unknown = JSON.parse(text);\n if (!Array.isArray(data)) throw new Error(\"the reply of the agent of Cilium is not a list of services\");\n return data as CiliumService[];\n};\n\n/**\n * This function gives the backends of a Service in the reply of `GET /v1/service` of the agent of\n * Cilium 1.20, as `ip:port`, sorted. The model is `ServiceSpec` of `api/v1/openapi.yaml`. The\n * frontend is the ClusterIP and the port of the rule, with the scope `external` or no scope.\n *\n * The backends are those in the state `active`, or with no state. If no backend is active, the\n * backends are those in the state `terminating`. The BPF reconciler gives the slots of a service\n * in this sequence (`pkg/loadbalancer/reconciler/bpf_reconciler.go`). Thus a new connection goes\n * only to them. kube-proxy does the same with the endpoints that serve and stop. The other\n * states take no new connection: `terminating-not-serving`, `quarantined` and `maintenance`.\n */\nexport const ciliumBackendsOf = (text: string, rule: Pick<ServiceRule, \"ip\" | \"port\">): string[] => {\n const ours = ciliumServices(text).filter((service) => {\n const front = service.spec?.[\"frontend-address\"];\n const protocol = (front?.protocol ?? \"tcp\").toLowerCase();\n return front?.ip === rule.ip && front.port === rule.port && (protocol === \"tcp\" || protocol === \"any\") && (front.scope ?? \"external\") === \"external\";\n });\n const backends = ours.flatMap((service) => service.spec?.[\"backend-addresses\"] ?? []);\n const of = (states: readonly (string | undefined)[]): string[] =>\n [...new Set(backends.filter((b) => states.includes(b.state) && b.ip !== undefined).map((b) => `${b.ip}:${b.port ?? 0}`))].sort();\n const active = of([\"active\", undefined]);\n return active.length > 0 ? active : of([\"terminating\"]);\n};\n\n/**\n * This function tells if a dump of the rules names the Service of a rule. The rules of kube-proxy\n * name it as `namespace/name`. The JSON of Cilium names it in the flags of its frontend.\n */\nexport const namesService = (mode: ServiceRule[\"mode\"], text: string, rule: Pick<ServiceRule, \"ns\" | \"svc\">): boolean => {\n if (mode !== \"cilium\") return text.includes(`${rule.ns}/${rule.svc}`);\n try {\n return ciliumServices(text).some((service) => service.spec?.flags?.namespace === rule.ns && service.spec.flags.name === rule.svc);\n } catch {\n return false;\n }\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\n\n/** A kube-proxy mode is the mode of kube-proxy on each node: iptables (the default of kind), IPVS or nftables. */\nexport type KubeProxyMode = \"iptables\" | \"ipvs\" | \"nftables\";\nexport const KUBE_PROXY_MODES: readonly KubeProxyMode[] = [\"iptables\", \"ipvs\", \"nftables\"];\n\n/**\n * A dataplane is the path of a Service on each node, and the mode of a cell. It is kube-proxy in one\n * of its modes, or `cilium`: Cilium is the CNI and replaces kube-proxy (`cilium.ts`).\n */\nexport type Dataplane = KubeProxyMode | \"cilium\";\nexport const DATAPLANES: readonly Dataplane[] = [...KUBE_PROXY_MODES, \"cilium\"];\n\n/** This function tells if a dataplane is a mode of kube-proxy. */\nexport const isKubeProxyMode = (mode: Dataplane): mode is KubeProxyMode => mode !== \"cilium\";\n\n/** A node image is the kind node image of one Kubernetes version, pinned by digest. */\nexport interface NodeImage {\n readonly kubernetes: string;\n readonly image: string;\n}\n\n/**\n * These are the node images of kind v0.33.0, from its release notes. The image of Kubernetes 1.37\n * is v1.37.0. The research names v1.37.1 for some defaults, thus a default that changed in the\n * patch release is a mismatch to record.\n */\nexport const NODE_IMAGES: Readonly<Record<string, NodeImage>> = {\n \"1.37\": {\n kubernetes: \"v1.37.0\",\n image: \"kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5\",\n },\n \"1.36\": {\n kubernetes: \"v1.36.4\",\n image: \"kindest/node:v1.36.4@sha256:099e049362a1526b2db71494e1947aae99bd16290d7c895f2b7ea312e3cbfaed\",\n },\n};\n\n/**\n * A scenario is one kind cluster as data. The lab generates the configuration of kind from it, and\n * nothing comes from a default of kind. The scenario gives the node count, the version, the\n * dataplane, the sync periods of kube-proxy, the CNI and the subnets.\n */\nexport interface KindScenario {\n /** This name names the cluster and its Docker network: lowercase letters, digits and dashes. */\n readonly name: string;\n /** This key selects the node image (`NODE_IMAGES`). */\n readonly kubernetes: string;\n /** This count is the number of workers. The cluster also has one control plane. */\n readonly workers: number;\n /** This dataplane is the path of a Service on each node. */\n readonly dataplane: Dataplane;\n /** This is kube-proxy and its sync periods, or null when Cilium replaces kube-proxy. */\n readonly kubeProxy: {\n readonly mode: KubeProxyMode;\n /** This time is the shortest interval between two syncs of the rules. */\n readonly minSyncPeriod: Quantity;\n /** This time is the interval of the full sync of the rules. */\n readonly syncPeriod: Quantity;\n } | null;\n /** This CNI is kindnet, the CNI of kind, beside kube-proxy. The CNI of the dataplane `cilium` is Cilium. */\n readonly cni: \"kindnet\" | \"cilium\";\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n /**\n * If this flag is true, each node has the label `topology.kubernetes.io/zone`. The last worker is\n * in the zone `ZONE_LOST`, and the other nodes are in the zone `ZONE_KEPT`.\n */\n readonly zoned?: boolean;\n}\n\n/** This label gives the zone of a node, as Kubernetes names it. */\nexport const ZONE_LABEL = \"topology.kubernetes.io/zone\";\n/** This zone holds the control plane and each worker but the last. A zone loss keeps it. */\nexport const ZONE_KEPT = \"zone-a\";\n/** This zone holds only the last worker. A zone loss loses it. */\nexport const ZONE_LOST = \"zone-b\";\n\n/** This function gives the zone of the node at the position `at` of `nodeNames`, in a cluster of `count` nodes. */\nexport const zoneOf = (at: number, count: number): string => (at === count - 1 && at > 0 ? ZONE_LOST : ZONE_KEPT);\n\n/**\n * This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values.\n * The dataplane `cilium` has no kube-proxy, and its CNI is Cilium.\n */\nexport const scenario = (name: string, mode: Dataplane, workers: number, kubernetes = \"1.37\", zoned = false): KindScenario => ({\n name,\n kubernetes,\n workers,\n dataplane: mode,\n kubeProxy: isKubeProxyMode(mode) ? { mode, minSyncPeriod: { value: 1, unit: \"s\" }, syncPeriod: { value: 30, unit: \"s\" } } : null,\n cni: isKubeProxyMode(mode) ? \"kindnet\" : \"cilium\",\n podSubnet: \"10.244.0.0/16\",\n serviceSubnet: \"10.96.0.0/16\",\n ...(zoned ? { zoned } : {}),\n});\n\nconst NAME = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;\n\n/**\n * This function gives the problems of a scenario. A problem is a name that kind refuses, no image for\n * the version, or a count out of range. A kube-proxy or a CNI that does not agree with the dataplane is a problem too.\n */\nexport const scenarioProblems = (s: KindScenario): string[] => {\n const problems: string[] = [];\n if (!NAME.test(s.name)) problems.push(`the name ${s.name} is not lowercase letters, digits and dashes of 1 to 32 characters`);\n if (NODE_IMAGES[s.kubernetes] === undefined) problems.push(`no node image is pinned for Kubernetes ${s.kubernetes}`);\n if (!Number.isInteger(s.workers) || s.workers < 0 || s.workers > 8) problems.push(`${s.workers} workers is not a whole number from 0 to 8`);\n if (cidrOverlap(s.podSubnet, s.serviceSubnet)) problems.push(`the pod subnet ${s.podSubnet} overlaps the service subnet ${s.serviceSubnet}`);\n if (isKubeProxyMode(s.dataplane)) {\n if (s.kubeProxy?.mode !== s.dataplane) problems.push(`the dataplane ${s.dataplane} needs kube-proxy in the mode ${s.dataplane}`);\n if (s.cni !== \"kindnet\") problems.push(`the dataplane ${s.dataplane} needs the CNI kindnet`);\n } else if (s.kubeProxy !== null || s.cni !== \"cilium\") problems.push(\"the dataplane cilium needs no kube-proxy and the CNI cilium\");\n return problems;\n};\n\n/** This function gives the names of the node containers, as kind names them: the control plane, then each worker. */\nexport const nodeNames = (s: KindScenario): string[] => [\n `${s.name}-control-plane`,\n ...Array.from({ length: s.workers }, (_, at) => `${s.name}-worker${at === 0 ? \"\" : at + 1}`),\n];\n\n/** This function gives a duration as kube-proxy reads it, in whole milliseconds: `1s`, `500ms`. */\nexport const goDuration = (amount: Quantity): string => {\n const ms = valueIn(amount, \"ms\");\n if (!Number.isInteger(ms) || ms < 0) throw new RangeError(`${ms} ms is not a whole number of milliseconds`);\n return ms % 1000 === 0 ? `${ms / 1000}s` : `${ms}ms`;\n};\n\n/** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. It refuses a scenario with no kube-proxy. */\nexport const kubeProxyPatch = (s: KindScenario): string => {\n const proxy = s.kubeProxy;\n if (proxy === null) throw new Error(`the scenario ${s.name} has no kube-proxy`);\n return [\n \"apiVersion: kubeproxy.config.k8s.io/v1alpha1\",\n \"kind: KubeProxyConfiguration\",\n `mode: ${proxy.mode}`,\n `${proxy.mode}:`,\n ` minSyncPeriod: ${goDuration(proxy.minSyncPeriod)}`,\n ` syncPeriod: ${goDuration(proxy.syncPeriod)}`,\n \"\",\n ].join(\"\\n\");\n};\n\n/** A kind configuration is the cluster file that `kind create cluster --config` reads. JSON is YAML, thus the lab writes it as JSON. */\nexport interface KindConfig {\n readonly kind: \"Cluster\";\n readonly apiVersion: \"kind.x-k8s.io/v1alpha4\";\n readonly networking: {\n readonly ipFamily: \"ipv4\";\n /** This mode is the mode of kube-proxy, or `none` for no kube-proxy. */\n readonly kubeProxyMode: KubeProxyMode | \"none\";\n readonly disableDefaultCNI: boolean;\n readonly podSubnet: string;\n readonly serviceSubnet: string;\n };\n readonly nodes: ReadonlyArray<{\n readonly role: \"control-plane\" | \"worker\";\n readonly image: string;\n readonly labels: Readonly<Record<string, string>>;\n }>;\n readonly kubeadmConfigPatches: readonly string[];\n}\n\n/**\n * This function generates the configuration of kind from a scenario. It refuses a scenario with a\n * problem. The dataplane `cilium` gets no kindnet, as the kind guide of Cilium 1.20.4 tells\n * (`Documentation/installation/kind-configure.rst`). It also gets no kube-proxy, because Cilium\n * replaces it (`Documentation/network/kubernetes/kubeproxy-free.rst`).\n */\nexport const kindConfig = (s: KindScenario): KindConfig => {\n const problems = scenarioProblems(s);\n if (problems.length > 0) throw new Error(`the scenario ${s.name}: ${problems.join(\"; \")}`);\n const image = NODE_IMAGES[s.kubernetes]!.image;\n return {\n kind: \"Cluster\",\n apiVersion: \"kind.x-k8s.io/v1alpha4\",\n networking: {\n ipFamily: \"ipv4\",\n kubeProxyMode: s.kubeProxy?.mode ?? \"none\",\n disableDefaultCNI: s.cni !== \"kindnet\",\n podSubnet: s.podSubnet,\n serviceSubnet: s.serviceSubnet,\n },\n nodes: nodeNames(s).map((node, at, all) => ({\n role: at === 0 ? \"control-plane\" : \"worker\",\n image,\n labels: {\n \"lab.systems/node\": node.slice(s.name.length + 1),\n ...(s.zoned === true ? { [ZONE_LABEL]: zoneOf(at, all.length) } : {}),\n },\n })),\n kubeadmConfigPatches: s.kubeProxy === null ? [] : [kubeProxyPatch(s)],\n };\n};\n\n/** This function gives the first and the last address of an IPv4 CIDR, as numbers. */\nexport const cidrRange = (cidr: string): [number, number] => {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\/(\\d+)$/.exec(cidr);\n if (match === null) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const octets = match.slice(1, 5).map(Number);\n const bits = Number(match[5]);\n if (octets.some((octet) => octet > 255) || bits > 32) throw new Error(`not an IPv4 CIDR: ${cidr}`);\n const base = octets.reduce((sum, octet) => sum * 256 + octet, 0);\n const size = 2 ** (32 - bits);\n const first = Math.floor(base / size) * size;\n return [first, first + size - 1];\n};\n\n/** This function tells if two IPv4 CIDRs share an address. */\nexport const cidrOverlap = (a: string, b: string): boolean => {\n const [a0, a1] = cidrRange(a);\n const [b0, b1] = cidrRange(b);\n return a0 <= b1 && b0 <= a1;\n};\n","import { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { gunzipSync } from \"node:zlib\";\n\n/**\n * A tool is a binary that the lab downloads, at one pinned release, for each platform. The lab\n * keeps it in the cache folder of this package and does not install it on the system. Each use\n * compares its sha256 with the pinned value.\n *\n * A tool with an `entry` comes in a gzipped tar file. The pinned sha256 is the sha256 of that file.\n * The cache keeps the file, and each use takes the entry from it again.\n */\nexport interface Tool {\n readonly name: \"kind\" | \"kubectl\" | \"istioctl\" | \"helm\";\n readonly version: string;\n /** These are the release assets, by the platform and the architecture of Node: `win32-x64`, `linux-x64`. */\n readonly assets: Readonly<Record<string, { readonly url: string; readonly sha256: string }>>;\n /** This name is the file of the tool in its gzipped tar file, or nothing for a tool that is its own asset. */\n readonly entry?: string | undefined;\n}\n\n/** 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. */\nexport const KIND: Tool = {\n name: \"kind\",\n version: \"v0.33.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-windows-amd64\",\n sha256: \"4b22adaa135368c5a465d56bbd8e520cbea87272a06ca00b6078e7b81515c9fc\",\n },\n \"linux-x64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-amd64\",\n sha256: \"aee6151561422756b764a4ae28e7f44cda5af5a9eead3cc9985112b1de8d8e0d\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-arm64\",\n sha256: \"20022bee6cfcd5086cb7234d218e3454e6090022f2a8f55d1fa7fcf42c3867a2\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-darwin-arm64\",\n sha256: \"0c8c7dbe5e23594a198b786c4bc13dacc101fa6196b0cb0b23a1ca44e61f4b4f\",\n },\n },\n};\n\n/** 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`. */\nexport const KUBECTL: Tool = {\n name: \"kubectl\",\n version: \"v1.37.0\",\n assets: {\n \"win32-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/windows/amd64/kubectl.exe\",\n sha256: \"4721b614a67bb4932a0369e61f4a323d8c6ca00943d3a2ff14837c124da06f0e\",\n },\n \"linux-x64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/amd64/kubectl\",\n sha256: \"6129359f4e1f3848a5572ccb0b26cf28b8ca08cef38c95a765b2f64a2c961a2f\",\n },\n \"linux-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/linux/arm64/kubectl\",\n sha256: \"922df28df248cc00a9e025f947704f1d1482de64ece54cfe57e61f19eaf1eef3\",\n },\n \"darwin-arm64\": {\n url: \"https://dl.k8s.io/release/v1.37.0/bin/darwin/arm64/kubectl\",\n sha256: \"583beedaebe422e71d3f1a96acef8b1fef86ea2f09a45ad01aa6c9ce287c1380\",\n },\n },\n};\n\n/**\n * This release of istioctl is 1.31.3, from 2026-10-22. It holds the charts of its own version, thus the\n * lab needs no other tool to install Istio. The sums are from the `.sha256` assets of the release. The\n * release has a zip file for Windows, and the lab reads only a gzipped tar file. Thus Windows has no asset.\n */\nexport const ISTIOCTL: Tool = {\n name: \"istioctl\",\n version: \"1.31.3\",\n entry: \"istioctl\",\n assets: {\n \"linux-x64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-amd64.tar.gz\",\n sha256: \"382c3004ce2e9c5ed3064b092ae0da7f6bbe30c766a1be282f45725add004860\",\n },\n \"linux-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-linux-arm64.tar.gz\",\n sha256: \"7476801623dbb900073cb1dba6a23b6a15c564b7e92b208aed36829315fefff1\",\n },\n \"darwin-arm64\": {\n url: \"https://github.com/istio/istio/releases/download/1.31.3/istioctl-1.31.3-osx-arm64.tar.gz\",\n sha256: \"881c3b443d9e458e8f19feed6ffa187dcfcb03dc032cb56068f7d1d0cd976c82\",\n },\n },\n};\n\n/**\n * This release of Helm is v3.22.1, from 2026-10-15. The lab installs Cilium from its chart with it.\n * The guide of Cilium 1.20.4 installs with Helm 3. The sums are from the `.sha256sum` files of\n * `get.helm.sh`. The tar file for Windows has `helm.exe`, not `helm`, thus Windows has no asset.\n */\nexport const HELM: Tool = {\n name: \"helm\",\n version: \"v3.22.1\",\n entry: \"helm\",\n assets: {\n \"linux-x64\": {\n url: \"https://get.helm.sh/helm-v3.22.1-linux-amd64.tar.gz\",\n sha256: \"896cf63684a42a723d61c980369d6e588330927deb13676ca0d85006dd6da0ea\",\n },\n \"linux-arm64\": {\n url: \"https://get.helm.sh/helm-v3.22.1-linux-arm64.tar.gz\",\n sha256: \"4c29e286b279d170fa048ddfc3e4e83dcd069ca9c52102766ba672d7149e694d\",\n },\n \"darwin-arm64\": {\n url: \"https://get.helm.sh/helm-v3.22.1-darwin-arm64.tar.gz\",\n sha256: \"e73da8a0b0d5193774fdff17ce6319c7436bb22ee229b45fd64f3c40368c753a\",\n },\n },\n};\n\n/**\n * This function gives the bytes of one file of a gzipped tar file. A header of the ustar format has\n * 512 bytes. It has the name at 0, the size in octal at 124, the type at 156 and the prefix of the\n * name at 345. The data follows the header, in blocks of 512 bytes. The match is on the last part of the\n * name. A link, a folder and an extended header are not a file, thus the search skips them.\n */\nexport const tarEntry = (gzipped: Uint8Array, entry: string): Uint8Array => {\n const tar = gunzipSync(gzipped);\n const text = (from: number, length: number): string => {\n const field = tar.subarray(from, from + length);\n const end = field.indexOf(0);\n return field.subarray(0, end === -1 ? field.length : end).toString(\"latin1\");\n };\n let at = 0;\n while (at + 512 <= tar.length) {\n const name = text(at, 100);\n if (name === \"\") break;\n const size = Number.parseInt(text(at + 124, 12).trim() || \"0\", 8);\n if (!Number.isFinite(size)) throw new Error(`the tar entry ${name} has no size in octal`);\n const type = String.fromCharCode(tar[at + 156] ?? 0);\n const prefix = text(at + 345, 155);\n const full = prefix === \"\" ? name : `${prefix}/${name}`;\n if ((type === \"0\" || type === \"\\0\") && full.split(\"/\").at(-1) === entry) return new Uint8Array(tar.subarray(at + 512, at + 512 + size));\n at += 512 + Math.ceil(size / 512) * 512;\n }\n throw new Error(`the tar file has no file ${entry}`);\n};\n\n/** This folder is the cache of the tools: `.cache/` in this package, which git ignores. `KIND_CACHE` names another folder. */\nexport const cacheFolder = (): string =>\n process.env[\"KIND_CACHE\"] ?? fileURLToPath(new URL(\"../.cache/\", import.meta.url));\n\n/** This function gives the platform key of this process, as the assets name it. */\nexport const platformKey = (platform: string = process.platform, arch: string = process.arch): string =>\n `${platform}-${arch}`;\n\n/** This function gives the sha256 of some bytes, in hexadecimal. */\nexport const sha256 = (bytes: Uint8Array): string => createHash(\"sha256\").update(bytes).digest(\"hex\");\n\n/** This function gives the path of a tool in the cache, for a platform. */\nexport const toolPath = (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): string =>\n join(cache, \"tools\", `${tool.name}-${tool.version}-${key}`, key.startsWith(\"win32\") ? `${tool.name}.exe` : tool.name);\n\n/** This function writes a file through a partial file, thus a stop in the middle leaves no file at the path. */\nexport const writeWhole = (path: string, bytes: Uint8Array, executable: boolean): void => {\n mkdirSync(dirname(path), { recursive: true });\n const partial = `${path}.partial`;\n writeFileSync(partial, bytes);\n if (executable) chmodSync(partial, 0o755);\n renameSync(partial, path);\n};\n\n/** These are the waits in ms before the second and the third try of a download. A runner can fail to connect for a moment. */\nexport const DOWNLOAD_WAITS: readonly number[] = [2_000, 8_000];\n\n/**\n * This function gives the bytes of a URL. It tries again after each wait when the connection fails or\n * the answer is a 5xx. It refuses each other answer that is not ok at once.\n */\nexport const download = async (url: string, waits: readonly number[] = DOWNLOAD_WAITS): Promise<Uint8Array> => {\n for (let attempt = 0; ; attempt++) {\n const response = await fetch(url, { redirect: \"follow\" }).catch((error: unknown) => (error instanceof Error ? error : new Error(String(error))));\n if (response instanceof Response && response.ok) return new Uint8Array(await response.arrayBuffer());\n const failure = response instanceof Response ? new Error(`the download of ${url} answered ${response.status}`) : response;\n const wait = waits[attempt];\n if ((response instanceof Response && response.status < 500) || wait === undefined) throw failure;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n};\n\n/**\n * This function gives the path of a tool, after a download when the cache does not have it. It\n * refuses bytes whose sha256 is not the pinned value, from the cache or from the download. For a\n * tool in a tar file, the cache keeps the tar file beside the tool. Each use examines the tar file,\n * and writes the tool again when its bytes are not those of the entry.\n */\nexport const ensureTool = async (tool: Tool, key: string = platformKey(), cache: string = cacheFolder()): Promise<string> => {\n const asset = tool.assets[key];\n if (asset === undefined) throw new Error(`${tool.name} ${tool.version} has no pinned asset for ${key}`);\n const path = toolPath(tool, key, cache);\n const executable = !key.startsWith(\"win32\");\n // the downloaded asset: the tool itself, or its tar file\n const kept = tool.entry === undefined ? path : `${path}.tar.gz`;\n let bytes: Uint8Array;\n if (existsSync(kept)) {\n bytes = readFileSync(kept);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${kept} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n if (tool.entry === undefined) return path;\n } else {\n bytes = await download(asset.url);\n const found = sha256(bytes);\n if (found !== asset.sha256) throw new Error(`${asset.url} has the sha256 ${found}, not the pinned ${asset.sha256}`);\n writeWhole(kept, bytes, executable && tool.entry === undefined);\n if (tool.entry === undefined) return path;\n }\n const binary = tarEntry(bytes, tool.entry);\n if (!existsSync(path) || sha256(readFileSync(path)) !== sha256(binary)) writeWhole(path, binary, executable);\n return path;\n};\n","import { execFile } from \"node:child_process\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { ciliumMode } from \"./cilium.ts\";\nimport type { ClusterManifest } from \"./record.ts\";\nimport type { KindScenario } from \"./scenario.ts\";\nimport { NODE_IMAGES, cidrOverlap, kindConfig, nodeNames } from \"./scenario.ts\";\nimport { KIND, KUBECTL, ensureTool } from \"./tools.ts\";\n\nconst run = promisify(execFile);\n\n/** This function starts a program and gives its standard output. A failure gives its standard error in the message. */\nexport const command = async (\n program: string,\n args: readonly string[],\n options: { readonly env?: Readonly<Record<string, string>>; readonly input?: string; readonly timeout?: number } = {},\n): Promise<string> => {\n try {\n const child = run(program, [...args], {\n env: { ...process.env, ...options.env },\n maxBuffer: 256 << 20,\n timeout: options.timeout ?? 600_000,\n windowsHide: true,\n });\n if (options.input !== undefined) {\n child.child.stdin?.end(options.input);\n }\n const { stdout } = await child;\n return stdout.toString();\n } catch (error) {\n const failed = error as { stderr?: string; stdout?: string; message?: string };\n throw new Error(`${program} ${args.join(\" \")} failed: ${(failed.stderr ?? failed.message ?? \"\").toString().trim().slice(0, 2000)}`);\n }\n};\n\n/** The credentials of the API, as a container on the network of the cluster reaches it. */\nexport interface Credentials {\n readonly server: string;\n readonly ca: string;\n readonly cert: string;\n readonly key: string;\n}\n\n/** A cluster is one kind cluster that the lab made, with its own kubeconfig and Docker network. */\nexport interface Cluster {\n readonly scenario: KindScenario;\n readonly dir: string;\n readonly kubeconfig: string;\n readonly network: string;\n readonly nodes: readonly string[];\n kubectl(args: readonly string[], input?: string): Promise<string>;\n /** This method applies Kubernetes objects, as JSON on standard input. */\n apply(objects: readonly unknown[]): Promise<void>;\n /** This method starts a command in a node container. */\n exec(node: string, argv: readonly string[]): Promise<string>;\n /** This method copies a local Docker image into each node. */\n load(image: string): Promise<void>;\n credentials(): Promise<Credentials>;\n manifest(): Promise<ClusterManifest>;\n delete(): Promise<void>;\n}\n\n/** This function gives the subnets of the Docker networks, by their names. */\nconst dockerSubnets = async (): Promise<Array<[string, string]>> => {\n const ids = (await command(\"docker\", [\"network\", \"ls\", \"-q\"])).split(/\\s+/).filter((id) => id !== \"\");\n if (ids.length === 0) return [];\n const info = JSON.parse(await command(\"docker\", [\"network\", \"inspect\", ...ids])) as Array<{\n Name: string;\n IPAM?: { Config?: Array<{ Subnet?: string }> | null };\n }>;\n return info.flatMap((network) =>\n (network.IPAM?.Config ?? [])\n .map((config) => config.Subnet ?? \"\")\n .filter((subnet) => /^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$/.test(subnet))\n .map((subnet): [string, string] => [network.Name, subnet]),\n );\n};\n\n/**\n * This function makes a kind cluster from a scenario, in a folder that holds its config, its\n * kubeconfig and the cache of kubectl. The cluster gets a Docker network of its own. The function\n * refuses a pod or service subnet that overlaps a Docker network, because that breaks the traffic\n * between the nodes.\n */\nexport const createCluster = async (scenario: KindScenario, dir: string): Promise<Cluster> => {\n const kind = await ensureTool(KIND);\n const kubectlPath = await ensureTool(KUBECTL);\n mkdirSync(dir, { recursive: true });\n const kubeconfig = join(dir, \"kubeconfig\");\n const network = `systems-${scenario.name}`;\n const config = kindConfig(scenario);\n for (const [name, subnet] of await dockerSubnets()) {\n if (name === network) continue;\n for (const ours of [scenario.podSubnet, scenario.serviceSubnet])\n if (cidrOverlap(ours, subnet)) throw new Error(`the subnet ${ours} overlaps the Docker network ${name} (${subnet})`);\n }\n const file = join(dir, \"kind.json\");\n writeFileSync(file, `${JSON.stringify(config, null, 2)}\\n`);\n const env = { KIND_EXPERIMENTAL_DOCKER_NETWORK: network };\n const nodes = nodeNames(scenario);\n const kubectl = (args: readonly string[], input?: string) =>\n command(kubectlPath, [\"--kubeconfig\", kubeconfig, \"--cache-dir\", join(dir, \"kubectl-cache\"), ...args], input === undefined ? {} : { input });\n const cluster: Cluster = {\n scenario,\n dir,\n kubeconfig,\n network,\n nodes,\n kubectl,\n async apply(objects) {\n await kubectl([\"apply\", \"-f\", \"-\"], JSON.stringify({ apiVersion: \"v1\", kind: \"List\", items: objects }));\n },\n exec: (node, argv) => command(\"docker\", [\"exec\", node, ...argv]),\n async load(image) {\n await command(kind, [\"load\", \"docker-image\", \"--name\", scenario.name, image], { env, timeout: 900_000 });\n },\n async credentials() {\n const internal = join(dir, \"kubeconfig.internal\");\n writeFileSync(internal, await command(kind, [\"get\", \"kubeconfig\", \"--internal\", \"--name\", scenario.name], { env }));\n const view = JSON.parse(\n await command(kubectlPath, [\"--kubeconfig\", internal, \"config\", \"view\", \"--raw\", \"-o\", \"json\"]),\n ) as {\n clusters: Array<{ cluster: { server: string; \"certificate-authority-data\": string } }>;\n users: Array<{ user: { \"client-certificate-data\": string; \"client-key-data\": string } }>;\n };\n return {\n server: view.clusters[0]!.cluster.server,\n ca: view.clusters[0]!.cluster[\"certificate-authority-data\"],\n cert: view.users[0]!.user[\"client-certificate-data\"],\n key: view.users[0]!.user[\"client-key-data\"],\n };\n },\n async manifest() {\n const nodesJson = JSON.parse(await kubectl([\"get\", \"nodes\", \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string }; status: { nodeInfo: Record<string, string> } }>;\n };\n // the mode of kube-proxy from its configuration, or the version and the settings of Cilium\n const config = scenario.kubeProxy === null ? \"cilium-config\" : \"kube-proxy\";\n const data = (JSON.parse(await kubectl([\"-n\", \"kube-system\", \"get\", \"configmap\", config, \"-o\", \"json\"])) as { data: Record<string, string> }).data;\n const mode = scenario.kubeProxy === null ? ciliumMode(data) : (/^mode:\\s*(\\S*)/m.exec(data[\"config.conf\"] ?? \"\")?.[1] ?? \"\");\n const version = JSON.parse(await kubectl([\"version\", \"-o\", \"json\"])) as { serverVersion?: { gitVersion?: string } };\n return {\n kind: (await command(kind, [\"version\"])).trim(),\n nodeImage: NODE_IMAGES[scenario.kubernetes]!.image,\n server: version.serverVersion?.gitVersion ?? \"\",\n mode: mode.replaceAll('\"', \"\"),\n docker: (await command(\"docker\", [\"version\", \"--format\", \"{{.Server.Version}}\"])).trim(),\n nodes: nodesJson.items.map((item) => ({\n name: item.metadata.name,\n kubelet: item.status.nodeInfo[\"kubeletVersion\"] ?? \"\",\n kernel: item.status.nodeInfo[\"kernelVersion\"] ?? \"\",\n runtime: item.status.nodeInfo[\"containerRuntimeVersion\"] ?? \"\",\n os: item.status.nodeInfo[\"osImage\"] ?? \"\",\n })),\n };\n },\n async delete() {\n await command(kind, [\"delete\", \"cluster\", \"--name\", scenario.name, \"--kubeconfig\", kubeconfig], { env });\n await command(\"docker\", [\"network\", \"rm\", network]).catch(() => undefined);\n },\n };\n // With no CNI, no node is ready before the install of Cilium (`installCilium`), thus kind does not wait.\n const wait = scenario.cni === \"kindnet\" ? [\"--wait\", \"180s\"] : [];\n await command(kind, [\"create\", \"cluster\", \"--name\", scenario.name, \"--config\", file, \"--kubeconfig\", kubeconfig, ...wait], { env, timeout: 900_000 });\n return cluster;\n};\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { HELM, cacheFolder, download, ensureTool, sha256, writeWhole } from \"./tools.ts\";\n\n/**\n * This module installs a Helm chart that is pinned by the sha256 of its file. The installs of Cilium\n * (`cilium.ts`) and of MetalLB (`metallb.ts`) use it. Helm keeps its own files in the folder of the\n * cluster, thus two clusters do not share a cache of Helm.\n */\n\n/** A pinned chart is a Helm chart: its name, its version, the URL of its file and the sha256 of that file. */\nexport interface PinnedChart {\n readonly name: string;\n readonly version: string;\n readonly url: string;\n readonly sha256: string;\n}\n\n/**\n * This function gives the path of a chart in the cache, after a download when the cache does not have it.\n * It refuses a download whose sha256 is not the pinned value. A file in the cache with a different\n * sha256 (a torn write) is downloaded again, and each write replaces the file in one step.\n */\nexport const ensurePinnedChart = async (\n chart: PinnedChart,\n cache: string = cacheFolder(),\n get: (url: string) => Promise<Uint8Array> = download,\n): Promise<string> => {\n const path = join(cache, \"charts\", `${chart.name}-${chart.version}.tgz`);\n if (existsSync(path) && sha256(readFileSync(path)) === chart.sha256) return path;\n const bytes = await get(chart.url);\n const found = sha256(bytes);\n if (found !== chart.sha256) throw new Error(`the chart ${chart.name} ${chart.version} has the sha256 ${found}, not the pinned ${chart.sha256}`);\n writeWhole(path, bytes, false);\n return path;\n};\n\n/**\n * This function installs a pinned chart in a cluster with the values `values`. It writes the values\n * to a file in the folder of the cluster. Helm waits for each workload of the chart, for up to 10 min.\n */\nexport const helmInstall = async (\n cluster: Pick<Cluster, \"dir\" | \"kubeconfig\">,\n release: string,\n chart: PinnedChart,\n namespace: string,\n values: Readonly<Record<string, unknown>>,\n options: { readonly createNamespace?: boolean } = {},\n): Promise<void> => {\n const helm = await ensureTool(HELM);\n const path = await ensurePinnedChart(chart);\n const file = join(cluster.dir, `${release}-values.json`);\n writeFileSync(file, `${JSON.stringify(values, null, 2)}\\n`);\n const home = join(cluster.dir, \"helm\");\n const env = { HELM_CACHE_HOME: join(home, \"cache\"), HELM_CONFIG_HOME: join(home, \"config\"), HELM_DATA_HOME: join(home, \"data\") };\n const create = options.createNamespace === true ? [\"--create-namespace\"] : [];\n await command(helm, [\"install\", release, path, \"-n\", namespace, ...create, \"--kubeconfig\", cluster.kubeconfig, \"-f\", file, \"--wait\", \"--timeout\", \"10m\"], { env, timeout: 900_000 });\n};\n","import type { Cluster } from \"./cluster.ts\";\nimport type { PinnedChart } from \"./helm.ts\";\nimport { ensurePinnedChart, helmInstall } from \"./helm.ts\";\nimport { cacheFolder } from \"./tools.ts\";\n\n/**\n * This module installs Cilium 1.20.4 in a kind cluster with Helm, from images pinned by digest.\n * Cilium is the CNI and replaces kube-proxy (the dataplane `cilium`). Each value of the install\n * that is not a default of the chart is in `ciliumValues`, with its reason.\n */\n\n/** This version is the release of Cilium of the lab, from 2026-10-23: the version of the chart and the tag of each image. */\nexport const CILIUM_VERSION = \"1.20.4\";\n\n/** This chart is the Helm chart of Cilium 1.20.4. The sha256 is of the file on `helm.cilium.io` (read on 2026-10-27). */\nexport const CILIUM_CHART: PinnedChart = {\n name: \"cilium\",\n version: CILIUM_VERSION,\n url: \"https://helm.cilium.io/cilium-1.20.4.tgz\",\n sha256: \"505abff8385811df1a949776fc42e9f10697989a332acc86d6e8933efcb855ed\",\n};\n\n/**\n * These are the images that the install needs, pinned by the digests that the chart of 1.20.4 names\n * (`values.yaml`). The agent, the operator and Envoy are the only images: the install has no\n * node init, no preflight and no Hubble relay. The lab pulls each one by its digest, tags it\n * (`localPin`), and loads it into each node. Thus no node pulls from a registry.\n */\nexport const CILIUM_IMAGES: { readonly agent: string; readonly operator: string; readonly envoy: string } = {\n agent: \"quay.io/cilium/cilium:v1.20.4@sha256:20fe92e4c86641233a450098c158d2d29085f3419931ab6bf681d2485dedf16e\",\n operator: \"quay.io/cilium/operator-generic:v1.20.4@sha256:7a7a3489dc66ad67e420d285c8a8d2e169b0957ad15e120f593d27081bf38d82\",\n envoy:\n \"quay.io/cilium/cilium-envoy:v1.39.5-1792733054-8c73b2816bc8239e27f1fd53b271884688137459@sha256:547ab45f6092f681d9f54f3be9fa8fb1e705c9c5dc2df388efe3a52aa6965629\",\n};\n\n/** These are the local tags of the images of Cilium, after the pull by digest. */\nexport interface CiliumImages {\n readonly agent: string;\n readonly operator: string;\n readonly envoy: string;\n}\n\n/**\n * This function gives the Helm values of the install in a cluster. The values that are not the\n * defaults of the chart:\n * - `kubeProxyReplacement: true`, and the address of the API server on the control plane. With no\n * kube-proxy, the agent cannot reach the API through its Service\n * (`Documentation/network/kubernetes/kubeproxy-free.rst`).\n * - `ipam.mode: kubernetes`: the agent takes the pod CIDR of each node from the node, as the kind\n * guide tells (`Documentation/installation/kind.rst`). Thus the pod subnet of the scenario holds.\n * - Each image is an `override`: the helper `cilium.image` of the chart then uses it as it is\n * (`templates/_helpers.tpl`). `pullPolicy: Never`: each image is on each node.\n * - The operator has one replica, not two. Its anti-affinity puts each replica on a different node,\n * and no cell examines a failover of the operator.\n *\n * Socket LB stays at its default. KPR sets it on (`pkg/kpr/kpr.go`). It selects a backend at\n * `connect()`. A connection keeps its backend, as with the conntrack NAT of kube-proxy. On the\n * deletion of a backend, the agent destroys only its UDP sockets, because\n * `LBSockTerminateAllProtos` is off by default (`pkg/loadbalancer/reconciler/termination.go`).\n */\nexport const ciliumValues = (clusterName: string, images: CiliumImages): Record<string, unknown> => ({\n kubeProxyReplacement: true,\n k8sServiceHost: `${clusterName}-control-plane`,\n k8sServicePort: 6443,\n ipam: { mode: \"kubernetes\" },\n image: { override: images.agent, pullPolicy: \"Never\" },\n operator: { replicas: 1, image: { override: images.operator, pullPolicy: \"Never\" } },\n envoy: { image: { override: images.envoy, pullPolicy: \"Never\" } },\n});\n\n/** These are the workloads in kube-system that the install waits for, then the DNS that waits for the CNI. */\nexport const CILIUM_WORKLOADS: readonly string[] = [\"daemonset/cilium\", \"deployment/cilium-operator\", \"daemonset/cilium-envoy\"];\n\n/** This function gives the path of the chart in the cache, after a download when the cache does not have it. It refuses a file whose sha256 is not the pinned value. */\nexport const ensureChart = (cache: string = cacheFolder()): Promise<string> => ensurePinnedChart(CILIUM_CHART, cache);\n\n/**\n * This function installs Cilium in a cluster with no CNI and no kube-proxy. Helm keeps its own\n * files in the folder of the cluster. The function waits for each workload, for each node to be\n * ready, and for CoreDNS. If the install fails, the error has the pods of kube-system and the last\n * lines of the log of the agent.\n */\nexport const installCilium = async (cluster: Cluster, images: CiliumImages): Promise<void> => {\n try {\n await helmInstall(cluster, \"cilium\", CILIUM_CHART, \"kube-system\", ciliumValues(cluster.scenario.name, images));\n for (const workload of CILIUM_WORKLOADS) await cluster.kubectl([\"-n\", \"kube-system\", \"rollout\", \"status\", workload, \"--timeout=300s\"]);\n await cluster.kubectl([\"wait\", \"--for=condition=Ready\", \"nodes\", \"--all\", \"--timeout=300s\"]);\n await cluster.kubectl([\"-n\", \"kube-system\", \"rollout\", \"status\", \"deployment/coredns\", \"--timeout=300s\"]);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", \"kube-system\", ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const agent = await state([\"logs\", \"daemonset/cilium\", \"-c\", \"cilium-agent\", \"--tail=80\"]);\n throw new Error(`the install of Cilium failed: ${String(error)}\\nthe pods of kube-system:\\n${pods}\\nthe log of the agent:\\n${agent}`);\n }\n};\n\n/** This is the setting of Cilium that a cluster manifest records: the version and the keys of `cilium-config` that the cells depend on. */\nexport const CILIUM_CONFIG_KEYS: readonly string[] = [\"kube-proxy-replacement\", \"bpf-lb-sock\", \"bpf-lb-algorithm\", \"routing-mode\", \"ipam\"];\n\n/** This function gives the mode of a cluster manifest of the dataplane `cilium`, from the data of the configmap `cilium-config`. */\nexport const ciliumMode = (data: Readonly<Record<string, string>>): string =>\n `cilium ${CILIUM_VERSION} ${CILIUM_CONFIG_KEYS.map((key) => `${key}=${data[key] ?? \"default\"}`).join(\" \")}`;\n","// The composition of the service of a kind cell. The app of the image and `lint()` compose the same\n// service from the same declaration: the image copies this file, and Node strips its types. Thus\n// this file imports only packages and types.\nimport type { CheckFn, Coupling, Health, HealthServer, ProbeAnswer, ServiceBuilder, TransportImpl } from \"@mark1russell7/service\";\nimport { service } from \"@mark1russell7/service\";\nimport type { RedisClient } from \"@mark1russell7/service/redis\";\nimport type { Declaration } from \"./cell.ts\";\n\n/** These are the parts of a running service. A composition for `lint()` gives none of them. */\nexport interface Parts {\n readonly redis?: RedisClient | undefined;\n readonly flag?: CheckFn | undefined;\n readonly http?: TransportImpl | undefined;\n readonly health?: HealthServer | undefined;\n}\n\n/** This name is the controller of the service, and its one route reads Redis when the service has Redis. */\nexport const CONTROLLER = \"work\";\nexport const ROUTE = \"GET /work\";\n/** This name is the check that a flag file fails: the seam of the liveness restart. */\nexport const FLAG_CHECK = \"self.flag\";\n/** This name is the watchdog of the event loop. */\nexport const WATCHDOG = \"self.event-loop\";\n\n/**\n * This function gives the health server of the shared gate (S6, candidate E). Its readiness probe\n * leaves out each check of `dependency`, a dependency that each replica shares. The controller still\n * reads each check, thus the service still refuses each request at once with a 503. The other\n * probes, and the readiness of a controller, answer as before.\n *\n * The probe passes when the service is started and not in the drain, and liveness does not fail\n * readiness through a coupling. Each readiness check of another dependency, or of no dependency,\n * must also be released.\n */\nexport const sharedGate = (server: HealthServer, dependency: string, couplings: readonly Coupling[]): HealthServer => {\n const gated = (health: Health): Health => ({\n verdict: () => health.verdict(),\n metrics: () => health.metrics(),\n subscribe: (listener) => health.subscribe(listener),\n forceFail: (name, on) => health.forceFail(name, on),\n drain: () => health.drain(),\n stop: () => health.stop(),\n probe(kind, controller): ProbeAnswer {\n const answer = health.probe(kind, controller);\n if (kind !== \"readiness\" || controller !== undefined) return answer;\n const verdict = health.verdict();\n const coupled = couplings.some((coupling) => coupling.from === \"liveness\" && coupling.to === \"readiness\") && !verdict.alive;\n const checks = Object.values(verdict.checks).every((check) => check.gates !== \"readiness\" || check.dependency === dependency || check.tripped !== true);\n const dependencies = Object.entries(verdict.dependencies).every(([name, up]) => name === dependency || up);\n const pass = verdict.started && !verdict.draining && !coupled && checks && dependencies;\n return { ...answer, status: pass ? 200 : 503, body: { ...answer.body, status: pass ? \"pass\" : \"fail\" } };\n },\n });\n return { listen: (health) => server.listen(gated(health)), close: () => server.close() };\n};\n\n/**\n * This function composes the service of a declaration: its policies, its couplings, its checks,\n * one server, one controller and each stage of the shutdown. A declaration with Redis needs the\n * client, because the client declares the dependency and its checks.\n */\nexport const compose = (name: string, declaration: Declaration, parts: Parts = {}): ServiceBuilder => {\n const svc = service(name)\n .readiness(declaration.readiness)\n .liveness(declaration.liveness)\n .startup(declaration.startup)\n .cooperativeWindow(declaration.cooperativeWindow)\n // the kubelet sends SIGTERM one time and SIGKILL at the end of the grace: a repeat signal has no effect\n .onRepeatSignal(\"ignore\");\n for (const coupling of declaration.couplings) svc.couple(coupling);\n if (declaration.redis !== null) {\n if (parts.redis === undefined) throw new Error(`${name} declares Redis, thus its composition needs the client`);\n parts.redis.attach(svc, { poll: declaration.poll, steps: { \"round-trip\": declaration.redis.gates } });\n }\n if (declaration.flag !== null)\n svc.check(\n {\n name: FLAG_CHECK,\n dependency: null,\n step: \"local-state\",\n ownConnection: true,\n gates: declaration.flag,\n poll: declaration.poll,\n },\n parts.flag,\n );\n if (declaration.watchdog !== null)\n svc.watchdog({\n name: WATCHDOG,\n tick: declaration.watchdog.tick,\n threshold: declaration.watchdog.threshold,\n gates: \"liveness\",\n poll: declaration.poll,\n });\n svc.server(\"http\", {}, parts.http);\n const controller = svc.controller(CONTROLLER);\n if (declaration.redis !== null) controller.requires(parts.redis!.name);\n controller.route(ROUTE, { idempotent: true });\n // the shared gate keeps the checks of Redis out of the readiness that the kubelet reads\n if (declaration.sharedGate === true && declaration.redis === null) throw new Error(`${name} has the shared gate, but it declares no Redis`);\n if (parts.health !== undefined)\n svc.healthServer(declaration.sharedGate === true ? sharedGate(parts.health, parts.redis!.name, declaration.couplings) : parts.health);\n for (const [stage, cap] of declaration.phases) svc.phase(stage, cap);\n return svc;\n};\n","// The hosts of one cluster in the Envoy of a sidecar, from the text of its admin endpoint\n// `/clusters`. The agent of the lab image reads it with Node, thus this module imports nothing.\n\n/**\n * These are the hosts of a cluster: the healthy ones (`b`, in sequence), and each other host with its\n * health flags (`u`). Each host is `ip:port`.\n */\nexport interface ClusterHosts {\n readonly b: readonly string[];\n readonly u: Readonly<Record<string, string>>;\n}\n\n/**\n * This function gives the name of the outbound cluster of a Service port in a sidecar of Istio:\n * `outbound|<port>||<service>.<namespace>.svc.cluster.local`.\n */\nexport const outboundCluster = (service: string, namespace: string, port: number): string => `outbound|${port}||${service}.${namespace}.svc.cluster.local`;\n\n/** This function gives the filter of `/clusters` that selects one cluster: a regular expression of RE2 with anchors. */\nexport const clusterFilter = (cluster: string): string => `^${cluster.replace(/[.|\\\\^$*+?()[\\]{}]/g, (c) => `\\\\${c}`)}$`;\n\n/**\n * This function gives the hosts of a cluster from the text of `/clusters`. Each host of the text has\n * a line `<cluster>::<ip:port>::health_flags::<flags>`. The flags are `healthy`, or the names of the\n * flags that the host has (`clusters_handler.cc` and `host_utility.cc` of Envoy v1.39.4).\n */\nexport const hostsOf = (text: string, cluster: string): ClusterHosts => {\n const healthy: string[] = [];\n const other: Record<string, string> = {};\n const prefix = `${cluster}::`;\n for (const line of text.split(\"\\n\")) {\n if (!line.startsWith(prefix)) continue;\n const parts = line.slice(prefix.length).split(\"::\");\n if (parts.length !== 3 || parts[1] !== \"health_flags\") continue;\n const [host, , flags] = parts as [string, string, string];\n if (flags.trim() === \"healthy\") healthy.push(host);\n else other[host] = flags.trim();\n }\n return { b: healthy.sort(), u: other };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Flap, KindCell } from \"./cell.ts\";\nimport { CLIENT, REDIS, SERVER } from \"./names.ts\";\nimport { HBONE_PORT } from \"./observe.ts\";\n\n/**\n * This module maps each trigger of a cell to the acts of the lab, as data. The runner finds each\n * target at the time of the run. A target is a pod, the PID of a container, the network namespace\n * of a pod, or a cgroup.\n */\n\n/**\n * A target is a process or a pod of the cell:\n * - `server` is the first replica of the service, in the sequence of its name. The injector acts in\n * its network namespace only, with each client pod as a peer: the partition of one replica.\n * - `redis` is the Redis pod.\n * - `kube-proxy` is kube-proxy on the first worker.\n * - `speaker` is the speaker of MetalLB on the owner of the VIP (SYS-57). Only a signal takes it.\n * - `broker` is the JVM of the Pulsar broker that owns the topic of the cell (`KindCell.pulsar`).\n * - `servers` is each replica of the service. Only the injector, a middlebox and a sysctl take it.\n * Each one acts in the network namespace of each replica, with each client pod as a peer. A REJECT\n * resets only the sender of the segment that it matches, thus the rule goes where the segments of\n * the clients arrive.\n * - `clients` is the process of each client pod. Only a signal, a sysctl and a drop of DNS take it.\n * They are the gap of `idle`, a sysctl of the namespace of each client pod, and `dns-blackhole`.\n */\nexport type Target = \"server\" | \"redis\" | \"kube-proxy\" | \"broker\" | \"servers\" | \"clients\" | \"speaker\";\n\n/** A step is one act of a fault, in the sequence of the plan. */\nexport type Step =\n | { readonly do: \"kubectl\"; readonly args: readonly string[]; readonly background?: boolean }\n /**\n * This step sends a signal to the target. For the target `clients`, it sends it to each client\n * process, the nodes at the same time. SIGUSR1 stops the calls of a gRPC client, and SIGUSR2 starts\n * them again. SIGHUP stops the calls and closes the channel, and the next SIGUSR2 opens a new channel.\n */\n | { readonly do: \"signal\"; readonly target: Target; readonly signal: \"SIGKILL\" | \"SIGSTOP\" | \"SIGCONT\" | \"SIGUSR1\" | \"SIGUSR2\" | \"SIGHUP\" }\n /** This step sends SIGSTOP, then SIGCONT `time` later, as the VM times it. The next step starts at once. */\n | { readonly do: \"pause\"; readonly target: Target; readonly time: Quantity }\n /**\n * This step starts the injector of the harness in the namespace of the target, with each replica of\n * the service as a peer. For the target `servers`, it starts it in the namespace of each replica,\n * with each client pod as a peer. The rules of all replicas commit at one instant. For the target\n * `server`, it starts it in the namespace of the target replica only, with each client pod as a peer.\n */\n | { readonly do: \"inject\"; readonly target: Target; readonly action: \"blackhole\" | \"clear\" | \"rst\" | \"clear-rst\" }\n | { readonly do: \"flag\"; readonly target: Target; readonly on: boolean }\n | { readonly do: \"cpu\"; readonly target: Target; readonly max: string }\n /** This step blocks the event loop of the target for `time`. */\n | { readonly do: \"stall\"; readonly target: Target; readonly time: Quantity }\n | { readonly do: \"wait\"; readonly time: Quantity }\n /** This step applies one object with `kubectl apply`. The runner fills the marks of its strings, for example `{namespace}`. */\n | { readonly do: \"apply\"; readonly object: Readonly<Record<string, unknown>> }\n /**\n * This step sets a sysctl of the network namespace of the target: each client pod (`clients`) or\n * each replica (`servers`). The injector of the harness enters each namespace in turn (`sysctlCommand`).\n */\n | { readonly do: \"sysctl\"; readonly target: Target; readonly key: string; readonly value: string }\n /**\n * This step puts a stateful middlebox in the network namespace of each replica (`servers`). Each\n * client pod is a peer, on the local port `port` (`middleboxCommands`). With `idle` null, the step\n * removes the middlebox and its chains.\n */\n | { readonly do: \"middlebox\"; readonly target: Target; readonly idle: Quantity | null; readonly port: number }\n /**\n * This step reads the workload certificates of the ztunnel pod of each node with istioctl. It adds a\n * line `certificates` with the validity of each leaf (`ztunnelCertificates`). It changes nothing.\n */\n | { readonly do: \"certificates\" }\n /**\n * This step loses the zone of the last worker (`lost`), or gives it back. The agent of the node\n * drops each packet on the link of the node, in its root network namespace. Then it freezes the\n * cgroup of the node. Thus each pod of the node and its kubelet stop at one instant. The step with `lost`\n * false thaws the cgroup, and then it removes the drops. It does nothing to a zone that is not lost.\n */\n | { readonly do: \"zone\"; readonly lost: boolean }\n /**\n * This step puts a qdisc netem on the link of the target, in its network namespace: each packet\n * that the target sends waits `delay`. A step on a link with a netem changes its delay, and the\n * packets in the queue keep their time. With `delay` null, the step removes the qdisc and each\n * packet in it. Only the target `redis` takes it.\n */\n | { readonly do: \"netem\"; readonly target: Target; readonly delay: Quantity | null }\n /**\n * This step writes the placement of the sessions of each client pod (`placementOf`): the backend of\n * each slot. `onTarget` of the sessions of all clients go to the target. The next SIGHUP of the\n * clients opens each slot again until its first answer comes from its backend.\n */\n | { readonly do: \"place\"; readonly onTarget: number }\n /**\n * This step drops each answer of a DNS server in the network namespace of each client pod\n * (`clients`), the nodes at the same time (`dnsCommands`). With `on` false, it removes the drops.\n */\n | { readonly do: \"dns\"; readonly target: Target; readonly on: boolean };\n\n/**\n * A fault plan is the steps of a run:\n * - `prepare` comes before the fault, and no reference comes from it: a sysctl, a middlebox, or a\n * stop of the calls. A plan without it has no such steps.\n * - `inject` starts the trigger. The stamp of its first act is the `fault` reference.\n * - `heal` ends the fault, `hold` after the fault. A trigger that ends by itself has no heal.\n * - `restore` brings the cell back to its steady state after the run.\n * - `flaps` are the faults after the first one. A plan without them has one fault.\n */\nexport interface FaultPlan {\n readonly prepare?: readonly Step[] | undefined;\n readonly inject: readonly Step[];\n readonly hold: Quantity | null;\n readonly heal: readonly Step[];\n readonly restore: readonly Step[];\n /** These flaps are the faults after the first one (`Trigger.flaps`). Each one does the inject and the heal again. */\n readonly flaps?: readonly Flap[] | undefined;\n}\n\nconst needs = (time: Quantity | undefined, what: string): Quantity => {\n if (time === undefined) throw new Error(`the trigger needs ${what}`);\n return time;\n};\n\n/** This function gives an amount in microseconds as a whole number, for `cpu.max`. */\nconst micros = (amount: Quantity): number => Math.round(valueIn(amount, \"us\"));\n\n/** This value marks the name of the target pod in the arguments of kubectl. The runner replaces it. */\nexport const POD = \"{pod}\";\n/** This value marks the node of the target pod in the arguments of kubectl. */\nexport const NODE = \"{node}\";\n/** This value marks the next version of the Deployment, for a rollout. */\nexport const VERSION = \"{version}\";\n/** This annotation of a pod is its deletion cost for its ReplicaSet: a lower cost goes first at a scale-in. */\nexport const DELETION_COST = \"controller.kubernetes.io/pod-deletion-cost\";\n/** This name is the name of the AuthorizationPolicy of the trigger `authorization-policy`. */\nexport const POLICY = \"lab-deny\";\n/** This service account is the account of the clients: their pods name no other account. */\nexport const CLIENT_ACCOUNT = \"default\";\n/** This selector gives the ztunnel pods in istio-system: the label `app` of the chart of ztunnel. */\nexport const ZTUNNEL_PODS = \"app=ztunnel\";\n\n/**\n * This function gives the AuthorizationPolicy of the trigger `authorization-policy`. It denies each\n * request to the server pods from the principal of the service account `account`. Istio gives a\n * pod of the mesh the principal `cluster.local/ns/<namespace>/sa/<account>` of its account. The\n * RBAC filter of the server sidecar reads the principal of the peer from its mTLS certificate.\n */\nexport const policyObject = (account: string): Readonly<Record<string, unknown>> => ({\n apiVersion: \"security.istio.io/v1\",\n kind: \"AuthorizationPolicy\",\n metadata: { name: POLICY, namespace: \"{namespace}\" },\n spec: {\n selector: { matchLabels: { app: SERVER } },\n action: \"DENY\",\n rules: [{ from: [{ source: { principals: [`cluster.local/ns/{namespace}/sa/${account}`] } }] }],\n },\n});\n\n/** These are the names of the sysctls that the steps of a plan set. Each one is a sysctl of a network namespace. */\nexport const SYSCTL = {\n retries: \"net.ipv4.tcp_retries2\",\n} as const;\n\n/**\n * These are the values of those sysctls in a new network namespace of Linux v6.17. A restore puts\n * them back. `tcp_retries2` is TCP_RETR2 (include/net/tcp.h, line 105, and net/ipv4/tcp_ipv4.c,\n * line 3604).\n */\nexport const NETNS_DEFAULTS = { retries: \"15\" } as const;\n\n/**\n * These are the names of the middlebox in the filter table of a replica. One chain takes the\n * segments from the peers, and one chain takes the segments to them. The table of xt_recent holds\n * the time of the last segment of each peer.\n */\nexport const MIDDLEBOX = { input: \"LAB-MBX-IN\", output: \"LAB-MBX-OUT\", table: \"LAB-MBX\" } as const;\n\n/** This time is between the stop of the calls and the fault of a quiet black hole. The last answers and their ACKs, at most 200 ms late, come in it. */\nexport const QUIET_LEAD: Quantity = { value: 1, unit: \"s\" };\n\n/** This time is between the close of the old channel of each client and the fault of a gap with `close`. The stream of the old channel ends in it. */\nexport const CLOSE_LEAD: Quantity = { value: 2, unit: \"s\" };\n\n/** This time is how long each client calls on its new channel at the fault of a gap with `close`, before it closes it. */\nexport const CHANNEL_USE: Quantity = { value: 5, unit: \"s\" };\n\n/**\n * This time is between the SIGHUP of `cannot-serve` and its fault. Each client opens new connections\n * at the signal, and the requests on its old connections end in this time (the timeout and 100 ms).\n */\nexport const SPREAD_LEAD: Quantity = { value: 3, unit: \"s\" };\n\nconst IPV4 = /^(25[0-5]|2[0-4]\\d|1?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}$/;\n\n/**\n * This function gives the command of the injector that sets one sysctl of its network namespace.\n * Only a sysctl of the namespace (`net.`) and a whole number reach it.\n */\nexport const sysctlCommand = (key: string, value: string): string[] => {\n if (!/^net\\.[a-z0-9_.]+$/.test(key)) throw new Error(`not a sysctl of a network namespace: ${key}`);\n if (!/^\\d+$/.test(value)) throw new Error(`not a whole number: ${value}`);\n return [\"sysctl\", \"-w\", `${key}=${value}`];\n};\n\n/** This port is the port of a DNS server: each answer of the cluster DNS comes from it. */\nexport const DNS_PORT = 53;\n\n/**\n * This function gives the commands of the injector that drop each answer of a DNS server in its\n * network namespace, on UDP and on TCP. With `on` false, they remove the drops. The rules go in\n * INPUT, thus each query still goes out, and CoreDNS answers it. Only the answer is lost. A send of\n * the resolver gets no error, as for a DNS server with no route back. The rules match the source port,\n * thus each DNS server of the namespace is in the fault.\n */\nexport const dnsCommands = (on: boolean): string[][] => {\n const op = on ? \"-I\" : \"-D\";\n return ([\"udp\", \"tcp\"] as const).map((protocol) => [\"iptables\", op, \"INPUT\", \"-p\", protocol, \"--sport\", String(DNS_PORT), \"-j\", \"DROP\"]);\n};\n\n/**\n * This function gives the commands of the injector that put a stateful middlebox in its network\n * namespace, with each peer, on the local port `port`. With `idle` null, they remove the parts of these\n * peers. A run removes what the table of the namespace holds (`middleboxRemoval`).\n *\n * The middlebox forgets a flow of the port after `idle` with no segment. Then it drops each later\n * segment of it: the silent-drop form of the router of the harness. Conntrack stays as it is. The\n * local leg from ztunnel to the app has no PING, and istio-cni needs its conntrack entry. The\n * connmark of that entry sends each answer of the app back to ztunnel (cni/pkg/iptables/iptables.go\n * of Istio 1.31.3, lines 172–178, 278–284 and 336–347).\n *\n * The match recent keeps a table in each network namespace (xt_recent.c of Linux v6.17). Its key is\n * the address of the peer: the source of a segment from it, or the destination of a segment to it\n * (lines 251–257). The chain of each direction has these rules, in sequence:\n * - In the input chain only, a SYN sets the time of its peer and passes: a new flow.\n * - A segment of a peer with a time in the last `idle` seconds passes, and it sets the time again\n * (lines 298–321).\n * - A segment of a peer with an older time is dropped, and the time stays old. Thus the flow stays forgotten.\n * - A segment of a peer that the table does not hold sets its time and passes (lines 283–290). That\n * is the flow that was open before the set.\n *\n * Each rule that passes a segment goes back to INPUT or OUTPUT (RETURN), thus the other rules of the\n * namespace still act on it. The key is an address, not a flow. Thus a new SYN of a peer also refreshes an\n * old flow of the peer. A cell of the middlebox opens no new flow in its gap.\n */\nexport const middleboxCommands = (peers: readonly string[], idle: Quantity | null, port: number): string[][] => {\n if (peers.length === 0) throw new Error(\"a middlebox needs a peer\");\n for (const peer of peers) if (!IPV4.test(peer)) throw new Error(`not an address: ${peer}`);\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`not a port: ${port}`);\n const op = idle === null ? \"-D\" : \"-I\";\n // the option of a port follows the protocol that loads its match\n const jumps = peers.flatMap((peer) => [\n [\"iptables\", op, \"INPUT\", \"-s\", `${peer}/32`, \"-p\", \"tcp\", \"--dport\", String(port), \"-j\", MIDDLEBOX.input],\n [\"iptables\", op, \"OUTPUT\", \"-d\", `${peer}/32`, \"-p\", \"tcp\", \"--sport\", String(port), \"-j\", MIDDLEBOX.output],\n ]);\n const chains = [MIDDLEBOX.input, MIDDLEBOX.output];\n // the chains are empty first: a jump that a failed fault did not add does not keep the drops\n if (idle === null) return [...chains.map((chain) => [\"iptables\", \"-F\", chain]), ...jumps, ...chains.map((chain) => [\"iptables\", \"-X\", chain])];\n const time = valueIn(idle, \"s\");\n if (!Number.isInteger(time) || time < 1) throw new Error(`the idle timeout of the middlebox is a whole number of seconds, not ${time}`);\n const rules = (chain: string, side: \"--rsource\" | \"--rdest\"): string[][] => {\n const recent = [\"-m\", \"recent\", \"--name\", MIDDLEBOX.table, side];\n return [\n ...(chain === MIDDLEBOX.input ? [[\"iptables\", \"-A\", chain, \"-p\", \"tcp\", \"--syn\", ...recent, \"--set\", \"-j\", \"RETURN\"]] : []),\n [\"iptables\", \"-A\", chain, ...recent, \"--update\", \"--seconds\", String(time), \"-j\", \"RETURN\"],\n [\"iptables\", \"-A\", chain, ...recent, \"--rcheck\", \"-j\", \"DROP\"],\n [\"iptables\", \"-A\", chain, ...recent, \"--set\", \"-j\", \"RETURN\"],\n ];\n };\n return [...chains.map((chain) => [\"iptables\", \"-N\", chain]), ...rules(MIDDLEBOX.input, \"--rsource\"), ...rules(MIDDLEBOX.output, \"--rdest\"), ...jumps];\n};\n\n/**\n * This function gives the commands that remove a middlebox from a network namespace, from the filter\n * table that `iptables-save -c` gives for it. An empty list means that the namespace has no part of it.\n *\n * The commands remove each part that the table holds, and only those. The lab stops at the first\n * command that fails. Thus a removal from a list of the peers stops at a jump that is not there, and\n * its chains stay. For example, a set that failed half way added no jumps, or a client pod has a new\n * address. Then the next set of the namespace fails at its `-N`.\n */\nexport const middleboxRemoval = (saved: string): string[][] => {\n const chains = [MIDDLEBOX.input, MIDDLEBOX.output];\n const lines = saved.split(\"\\n\").map((line) => line.trim().replace(/^\\[\\d+:\\d+\\]\\s+/, \"\"));\n const present = chains.filter((chain) => lines.some((line) => line.startsWith(`:${chain} `)));\n const jumps = lines\n .map((line) => line.split(/\\s+/))\n .filter((words) => words[0] === \"-A\" && (words[1] === \"INPUT\" || words[1] === \"OUTPUT\") && chains.some((chain) => words.at(-1) === chain && words.at(-2) === \"-j\"))\n .map((words) => [\"iptables\", \"-D\", ...words.slice(1)]);\n return [...present.map((chain) => [\"iptables\", \"-F\", chain]), ...jumps, ...present.map((chain) => [\"iptables\", \"-X\", chain])];\n};\n\n/**\n * This function gives the target of a reset or a black hole of a cell. In T07 and T18 it is Redis,\n * with the replicas as peers. In T15 the fault is on the mesh leg between the client pods and the\n * replicas. It is a rule in each replica, with the client pods as peers. A partition (`partition`)\n * is a rule in the target replica only, with the client pods as peers.\n */\nconst linkOf = (cell: KindCell): Target => (cell.trigger.partition === true ? \"server\" : cell.template === \"T15\" ? \"servers\" : \"redis\");\n\n/**\n * This function gives the placement of the sessions of the clients: the backend of each slot of each\n * client, by client. The clients come in the sequence of their names. The sessions on the target go\n * one to each client in turn: the first slot of each client, then the second slot of each client. Thus\n * no client has two more sessions on the target than another. The other slots go to the other\n * replicas in turn.\n */\nexport const placementOf = (clients: readonly string[], connections: number, onTarget: number, target: string, others: readonly string[]): Map<string, string[]> => {\n if (!Number.isInteger(connections) || connections < 1) throw new RangeError(`a placement needs one session of each client at the least, not ${connections}`);\n if (!Number.isInteger(onTarget) || onTarget < 0 || onTarget > clients.length * connections)\n throw new RangeError(`${onTarget} sessions on the target is not a count of the ${clients.length * connections} sessions of the clients`);\n if (others.length === 0 && onTarget < clients.length * connections) throw new RangeError(\"a session off the target needs another replica\");\n const sorted = [...clients].sort((a, b) => a.localeCompare(b));\n let other = 0;\n return new Map(\n sorted.map((client, at) => [\n client,\n Array.from({ length: connections }, (_, slot) => (slot * sorted.length + at < onTarget ? target : others[other++ % others.length]!)),\n ]),\n );\n};\n\n/**\n * This function puts the step `place` before the first SIGHUP of the clients, for a workload with a\n * placement (`Workload.onTarget`). A plan with no such SIGHUP cannot place the sessions.\n */\nconst placed = (cell: KindCell, plan: FaultPlan): FaultPlan => {\n const onTarget = cell.workload.onTarget;\n if (onTarget === undefined) return plan;\n if (cell.workload.connections === undefined) throw new Error(`${cell.name}: a placement needs the sessions of each client (connections)`);\n const prepare = plan.prepare ?? [];\n const at = prepare.findIndex((step) => step.do === \"signal\" && step.target === \"clients\" && step.signal === \"SIGHUP\");\n if (at === -1) throw new Error(`${cell.name}: the trigger ${cell.trigger.kind} has no SIGHUP of the clients before its fault, thus it cannot place the sessions`);\n return { ...plan, prepare: [...prepare.slice(0, at), { do: \"place\", onTarget }, ...prepare.slice(at)] };\n};\n\n/**\n * This function gives the fault plan of a cell. A trigger with flaps (`Trigger.flaps`) gives them to\n * its plan: the runner then does the inject and the heal again for each flap. A plan with no hold or\n * no heal cannot flap.\n */\nexport const planOf = (cell: KindCell): FaultPlan => {\n const plan = placed(cell, stepsOf(cell));\n const flaps = cell.trigger.flaps;\n if (flaps === undefined || flaps.length === 0) return plan;\n if (plan.hold === null || plan.heal.length === 0) throw new Error(`the trigger ${cell.trigger.kind} has no hold and heal, thus it cannot flap`);\n for (const flap of flaps) {\n if (valueIn(flap.rest, \"ms\") <= 0 || valueIn(flap.hold, \"ms\") <= 0) throw new Error(\"each flap has a rest and a hold above zero\");\n }\n return { ...plan, flaps };\n};\n\n/**\n * This function gives the time from the first fault of a trigger to its last heal. It is the hold,\n * then the rest and the hold of each flap. A trigger with no hold gives zero.\n */\nexport const faultSpan = (trigger: Pick<KindCell[\"trigger\"], \"hold\" | \"flaps\">): Quantity => {\n const hold = trigger.hold === undefined ? 0 : valueIn(trigger.hold, \"s\");\n const flaps = (trigger.flaps ?? []).reduce((sum, flap) => sum + valueIn(flap.rest, \"s\") + valueIn(flap.hold, \"s\"), 0);\n return { value: Math.round((hold + flaps) * 1e6) / 1e6, unit: \"s\" };\n};\n\n/** This device is the link of a pod: the end of its veth pair in its network namespace. */\nexport const POD_LINK = \"eth0\";\n\n/**\n * This time is how long the heal of a delay waits from the delay of zero to the removal of the\n * qdisc. Each packet in the queue leaves in it, because each one waits the delay at the most. A delay\n * of a cell must be shorter than it.\n */\nexport const NETEM_DRAIN: Quantity = { value: 1, unit: \"s\" };\n\n/** This function gives the steps of the fault plan of a cell, with no flaps. */\nconst stepsOf = (cell: KindCell): FaultPlan => {\n const ns = [\"-n\", \"{namespace}\"];\n const trigger = cell.trigger;\n const replicas = cell.pod.replicas;\n switch (trigger.kind) {\n case \"pod-deletion\":\n return { inject: [{ do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] }], hold: null, heal: [], restore: [] };\n case \"rolling-update\":\n return {\n inject: [{ do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] }],\n hold: null,\n heal: [],\n restore: [],\n };\n case \"node-drain\":\n return {\n inject: [\n {\n do: \"kubectl\",\n args: [\"drain\", NODE, \"--ignore-daemonsets\", \"--delete-emptydir-data\", \"--timeout=180s\", `--pod-selector=app=${SERVER}`],\n background: true,\n },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"uncordon\", NODE] }],\n };\n case \"scale-in\":\n return {\n inject: [\n // the ReplicaSet removes a pod of a lower deletion cost first. The rank by readiness comes before\n // it, and the pods of a node come after it (pkg/controller/controller_utils.go at v1.37.0, lines\n // 853–874). The gate PodDeletionCost is on from 1.22 (pkg/features/kube_features.go, line 1953).\n // Thus the clients of gRPC on the first replica see its stop\n ...(cell.stack === \"grpc\" ? [{ do: \"kubectl\" as const, args: [...ns, \"annotate\", \"pod\", POD, `${DELETION_COST}=-1`, \"--overwrite\"] }] : []),\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas - 1}`] },\n ],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${SERVER}`, `--replicas=${replicas}`] }],\n };\n case \"crash\":\n // a cell with a broker stops the broker that owns its topic, not a replica of the service\n return { inject: [{ do: \"signal\", target: cell.pulsar === undefined ? \"server\" : \"broker\", signal: \"SIGKILL\" }], hold: null, heal: [], restore: [] };\n case \"liveness-restart\":\n // the container that the kubelet starts again has a new /tmp, thus the flag ends with it\n return { inject: [{ do: \"flag\", target: \"server\", on: true }], hold: null, heal: [], restore: [] };\n case \"freeze-process\":\n // a cell with a VIP freezes the speaker of the owner, with no other fault (SYS-57). Each flap is one more pause\n if (cell.vip !== undefined) {\n return {\n inject: [{ do: \"signal\", target: \"speaker\", signal: \"SIGSTOP\" }],\n hold: needs(trigger.hold, \"a hold, the time that the speaker is frozen\"),\n heal: [{ do: \"signal\", target: \"speaker\", signal: \"SIGCONT\" }],\n restore: [{ do: \"signal\", target: \"speaker\", signal: \"SIGCONT\" }],\n };\n }\n if (cell.template === \"T07\") {\n return {\n inject: [\n { do: \"pause\", target: \"kube-proxy\", time: needs(trigger.hold, \"a hold\") },\n { do: \"wait\", time: needs(trigger.lead, \"a lead\") },\n { do: \"kubectl\", args: [...ns, \"delete\", \"pod\", POD, \"--wait=false\"] },\n ],\n hold: null,\n heal: [],\n restore: [],\n };\n }\n return {\n inject: [{ do: \"signal\", target: \"redis\", signal: \"SIGSTOP\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"signal\", target: \"redis\", signal: \"SIGCONT\" }],\n restore: [],\n };\n case \"rst\":\n return {\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"rst\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear-rst\" }],\n restore: [],\n };\n case \"blackhole\": {\n // a quiet black hole stops the calls before the fault, and starts them again after the clear.\n // The restore puts back the tcp_retries2 of each client pod, and starts the calls of a client\n // that a failed heal left stopped\n const quiet = trigger.quiet === true;\n const retries = trigger.retries;\n if (retries !== undefined && !(Number.isInteger(retries) && retries >= 1 && retries <= 255)) throw new Error(`tcp_retries2 is a count from 1 to 255, not ${retries}`);\n const start: Step[] = quiet ? [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" }] : [];\n // a partition gives each client new connections first, as cannot-serve does\n const spread: Step[] = trigger.partition === true ? [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }] : [];\n const prepare: Step[] = [\n ...spread,\n ...(retries === undefined ? [] : [{ do: \"sysctl\" as const, target: \"clients\" as const, key: SYSCTL.retries, value: String(retries) }]),\n ...(quiet ? [{ do: \"signal\" as const, target: \"clients\" as const, signal: \"SIGUSR1\" as const }, { do: \"wait\" as const, time: QUIET_LEAD }] : []),\n ];\n return {\n ...(prepare.length === 0 ? {} : { prepare }),\n inject: [{ do: \"inject\", target: linkOf(cell), action: \"blackhole\" }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"inject\", target: linkOf(cell), action: \"clear\" }, ...start],\n restore: [...(retries === undefined ? [] : [{ do: \"sysctl\" as const, target: \"clients\" as const, key: SYSCTL.retries, value: NETNS_DEFAULTS.retries }]), ...start],\n };\n }\n case \"peer-gone-without-close\":\n // the rules drop the FIN of Redis, then Redis goes away with its namespace and its rules\n return {\n inject: [\n { do: \"inject\", target: \"redis\", action: \"blackhole\" },\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"app-stall\": {\n // in T07, each client opens new connections first, as a partition does. Thus they spread over the replicas in each run\n const spread: Step[] = cell.template === \"T07\" ? [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }] : [];\n return {\n ...(spread.length === 0 ? {} : { prepare: spread }),\n inject: [{ do: \"stall\", target: \"server\", time: needs(trigger.hold, \"a hold\") }],\n hold: null,\n heal: [],\n restore: [],\n };\n }\n case \"rollout\":\n // Redis is down for each pod, a new one too: the Service of Redis has no endpoint\n return {\n inject: [\n { do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=0\"] },\n { do: \"kubectl\", args: [...ns, \"set\", \"env\", `deployment/${SERVER}`, `LAB_VERSION=${VERSION}`] },\n ],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"scale\", `deployment/${REDIS}`, \"--replicas=1\"] }],\n restore: [],\n };\n case \"cpu-throttle\": {\n const max = trigger.cpuMax;\n if (max === undefined) throw new Error(\"the trigger needs its cpu.max\");\n // the kernel reads cpu.max as the quota and the period in microseconds\n return {\n inject: [{ do: \"cpu\", target: \"server\", max: `${micros(max.quota)} ${micros(max.period)}` }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"cpu\", target: \"server\", max: `max ${micros(max.period)}` }],\n restore: [],\n };\n }\n case \"authorization-policy\":\n // the restore deletes a policy that a failed heal left\n return {\n inject: [{ do: \"apply\", object: policyObject(trigger.account ?? CLIENT_ACCOUNT) }],\n hold: needs(trigger.hold, \"a hold\"),\n heal: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY] }],\n restore: [{ do: \"kubectl\", args: [...ns, \"delete\", \"authorizationpolicy\", POLICY, \"--ignore-not-found\"] }],\n };\n case \"idle\": {\n // the gap of the clients: the restore starts the calls of a client that a failed heal left stopped\n const hold = needs(trigger.hold, \"a hold, the time of the gap\");\n const start: Step = { do: \"signal\", target: \"clients\", signal: \"SIGUSR2\" };\n if (trigger.close === true) {\n // each client closes its channel before the fault. At the fault it opens a new channel, calls\n // on it, and closes it. Thus the fault is the last use of the HBONE connection of the client\n const close: Step = { do: \"signal\", target: \"clients\", signal: \"SIGHUP\" };\n return { prepare: [close, { do: \"wait\", time: CLOSE_LEAD }], inject: [start, { do: \"wait\", time: CHANNEL_USE }, close], hold, heal: [start], restore: [start] };\n }\n const middlebox = trigger.middlebox;\n // the middlebox is in the namespace of each replica from before the gap to the end of the run\n return {\n ...(middlebox === undefined ? {} : { prepare: [{ do: \"middlebox\" as const, target: \"servers\" as const, idle: middlebox, port: HBONE_PORT }] }),\n inject: [{ do: \"signal\", target: \"clients\", signal: \"SIGUSR1\" }],\n hold,\n heal: [start],\n restore: [start, ...(middlebox === undefined ? [] : [{ do: \"middlebox\" as const, target: \"servers\" as const, idle: null, port: HBONE_PORT }])],\n };\n }\n case \"ztunnel-restart\":\n // the ztunnel pod of each node goes with its grace. The flow of each client crosses the ztunnel\n // of its own node, thus each client meets the restart. The restore waits for the DaemonSet\n return {\n inject: [{ do: \"kubectl\", args: [\"-n\", \"istio-system\", \"delete\", \"pod\", \"-l\", ZTUNNEL_PODS, \"--wait=false\"] }],\n hold: null,\n heal: [],\n restore: [{ do: \"kubectl\", args: [\"-n\", \"istio-system\", \"rollout\", \"status\", \"daemonset/ztunnel\", \"--timeout=300s\"] }],\n };\n case \"certificate-expiry\":\n // the calls go on through the hold. The reads of the certificates are the evidence of their\n // lifetime, at the fault and at the heal. Thus a run shows each leaf that expired in the hold\n return {\n inject: [{ do: \"certificates\" }],\n hold: needs(trigger.hold, \"a hold, the time past the lifetime of the certificates\"),\n heal: [{ do: \"certificates\" }],\n restore: [],\n };\n case \"cannot-serve\":\n // each client opens its connections again before the fault, thus they spread over the replicas\n // in each run. The flag fails the route and the readiness of the target. The heal removes it\n return {\n prepare: [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }],\n inject: [{ do: \"flag\", target: \"server\", on: true }],\n hold: needs(trigger.hold, \"a hold, the time that the replica cannot serve\"),\n heal: [{ do: \"flag\", target: \"server\", on: false }],\n restore: [{ do: \"flag\", target: \"server\", on: false }],\n };\n case \"zone-loss\":\n // each client opens its connections again before the fault, as cannot-serve does. The node\n // lifecycle controller removes the replica of the lost zone. The restore gives back a zone\n // that a failed heal left lost\n return {\n prepare: [{ do: \"signal\", target: \"clients\", signal: \"SIGHUP\" }, { do: \"wait\", time: SPREAD_LEAD }],\n inject: [{ do: \"zone\", lost: true }],\n hold: needs(trigger.hold, \"a hold, the time that the zone is lost\"),\n heal: [{ do: \"zone\", lost: false }],\n restore: [{ do: \"zone\", lost: false }],\n };\n case \"latency\": {\n // the heal sets the delay to zero first: a removal of the qdisc drops each reply in its queue.\n // The restore removes a qdisc that a failed heal left, and it does nothing to a link without one\n const delay = needs(trigger.delay, \"a delay, the time that each packet of Redis waits\");\n if (valueIn(delay, \"ms\") <= 0 || valueIn(delay, \"ms\") >= valueIn(NETEM_DRAIN, \"ms\")) throw new Error(`the delay of latency is above zero and below ${valueIn(NETEM_DRAIN, \"s\")} s`);\n const zero: Step = { do: \"netem\", target: \"redis\", delay: { value: 0, unit: \"s\" } };\n const back: readonly Step[] = [zero, { do: \"wait\", time: NETEM_DRAIN }, { do: \"netem\", target: \"redis\", delay: null }];\n return {\n inject: [{ do: \"netem\", target: \"redis\", delay }],\n hold: needs(trigger.hold, \"a hold, the time that Redis is slow\"),\n heal: back,\n restore: back,\n };\n }\n case \"dns-blackhole\":\n // the drops of each client pod, and their removal at the heal. A restore after a good heal\n // would find no rule to delete, thus the plan has none\n return {\n inject: [{ do: \"dns\", target: \"clients\", on: true }],\n hold: needs(trigger.hold, \"a hold, the time that no answer of DNS gets to a client\"),\n heal: [{ do: \"dns\", target: \"clients\", on: false }],\n restore: [],\n };\n }\n};\n\n/** 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. */\nexport const deletes = (cell: KindCell): boolean =>\n [\"pod-deletion\", \"rolling-update\", \"node-drain\", \"scale-in\"].includes(cell.trigger.kind) ||\n (cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\" && cell.vip === undefined);\n\n/** These are the names of the objects of a cell that a plan names. */\nexport const OBJECTS: { readonly server: typeof SERVER; readonly client: typeof CLIENT; readonly redis: typeof REDIS } = {\n server: SERVER,\n client: CLIENT,\n redis: REDIS,\n};\n","import { execFileSync } from \"node:child_process\";\nimport { copyFileSync, existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { imageOf, type BuildImage } from \"@mark1russell7/harness\";\n\n/** This folder is the root of the package, and its `image/` folder has the files of the image. */\nconst PACKAGE = fileURLToPath(new URL(\"../\", import.meta.url));\n/** This folder is the root of the repository, when the package is in the workspace of the repository. */\nconst REPO = fileURLToPath(new URL(\"../../../\", import.meta.url));\n/** These are the packages of this repository that the image holds, in the sequence of their dependencies. */\nexport const WORKSPACE_PACKAGES: readonly [\"quantity\", \"algebra\", \"service\"] = [\"quantity\", \"algebra\", \"service\"];\n/** This folder of a staged context holds the tarball of each package of the workspace. */\nconst PACKED = \"workspace\";\n/** These are the files of the image, by their name in the context and their source in the package. */\nconst FILES: ReadonlyArray<readonly [string, string]> = [\n [\"Dockerfile\", \"image/Dockerfile\"],\n [\"package.json\", \"image/package.json\"],\n [\"npm-lock.json\", \"image/npm-lock.json\"],\n [\"app.mjs\", \"image/app.mjs\"],\n [\"client.mjs\", \"image/client.mjs\"],\n [\"client-grpc.mjs\", \"image/client-grpc.mjs\"],\n [\"agent.mjs\", \"image/agent.mjs\"],\n [\"watch.mjs\", \"image/watch.mjs\"],\n [\"compose.ts\", \"src/compose.ts\"],\n [\"backends.ts\", \"src/backends.ts\"],\n [\"conntrack.ts\", \"src/conntrack.ts\"],\n [\"drops.ts\", \"src/drops.ts\"],\n [\"eds.ts\", \"src/eds.ts\"],\n [\"lds.ts\", \"src/lds.ts\"],\n [\"client-lines.ts\", \"src/client-lines.ts\"],\n [\"place.ts\", \"src/place.ts\"],\n];\n\nconst shell = process.platform === \"win32\";\n\n/** This function tells if the package is in the workspace of its repository. From npm, it is not. */\nconst inWorkspace = (): boolean =>\n existsSync(join(REPO, \"pnpm-workspace.yaml\")) && WORKSPACE_PACKAGES.every((name) => existsSync(join(REPO, \"packages\", name, \"package.json\")));\n\n/**\n * This function gives the folder of each installed package that the image holds. Node finds a\n * package from the folder of a package that depends on it. The service depends on the algebra,\n * thus the search starts with the service.\n */\nconst installedFolders = (): Map<string, string> => {\n const folders = new Map<string, string>();\n for (const name of [...WORKSPACE_PACKAGES].reverse()) {\n const found = [PACKAGE, ...folders.values()].flatMap((from) => {\n try {\n return [dirname(realpathSync(createRequire(join(from, \"package.json\")).resolve(`@mark1russell7/${name}/package.json`)))];\n } catch {\n return [];\n }\n })[0];\n if (found === undefined) throw new Error(`@mark1russell7/${name} is not installed with @mark1russell7/kind`);\n folders.set(name, found);\n }\n return folders;\n};\n\n/**\n * This function stages the build context of the lab image. In the workspace, it builds the packages\n * of the repository and packs each one as npm gets it. From npm, it packs the installed packages.\n * Then it copies the files of the image.\n */\nexport const stageLabImage = (): string => {\n const context = mkdtempSync(join(tmpdir(), \"systems-kind-lab-\"));\n const packed = join(context, PACKED);\n mkdirSync(packed);\n if (inWorkspace()) {\n const filters = WORKSPACE_PACKAGES.flatMap((name) => [\"--filter\", `@mark1russell7/${name}`]);\n execFileSync(\"pnpm\", [...filters, \"build\"], { cwd: REPO, shell, stdio: \"ignore\" });\n for (const name of WORKSPACE_PACKAGES)\n execFileSync(\"pnpm\", [\"pack\", \"--pack-destination\", packed], { cwd: join(REPO, \"packages\", name), shell, stdio: \"ignore\" });\n } else {\n for (const folder of installedFolders().values())\n execFileSync(\"npm\", [\"pack\", \"--pack-destination\", packed, \"--ignore-scripts\"], { cwd: folder, shell, stdio: \"ignore\" });\n }\n for (const [at, from] of FILES) copyFileSync(join(PACKAGE, from), join(context, at));\n return context;\n};\n\n/**\n * This function gives the source of the lab image from a staged context, as a `build` source of\n * the harness. The resolver of the harness plans it and builds it, as it does each other image.\n * Thus each `FROM` has a digest, and each manifest of packages has a lockfile.\n */\nexport const labSource = (context: string): BuildImage => ({\n kind: \"build\",\n name: \"systems-kind-lab\",\n context,\n files: [...FILES.map(([at]) => at), PACKED],\n});\n\nlet built: Promise<string> | null = null;\n\n/**\n * This function gives the tag of the lab image, one time for each process. It stages the context,\n * and the resolver of the harness builds it with a tag from its content (`imageOf`). The stage\n * builds and packs packages, thus a tag that passed stays. A stage or a build that fails leaves\n * the cache. Thus a later call stages and builds again after a fault of Docker or of the network.\n */\nexport const labImage = (): Promise<string> => {\n if (built !== null) return built;\n const building = (async () => {\n const context = stageLabImage();\n try {\n // the resolver copies the files into a context of its own before it builds\n return await imageOf(labSource(context));\n } finally {\n rmSync(context, { recursive: true, force: true });\n }\n })();\n // a failed build does not stay: the next caller tries again\n building.catch(() => {\n if (built === building) built = null;\n });\n built = building;\n return building;\n};\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { KindCell, MeshFlavor } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { outboundCluster } from \"./eds.ts\";\nimport { ZTUNNEL_PODS } from \"./faults.ts\";\nimport { SERVER } from \"./names.ts\";\nimport { ISTIOCTL, ensureTool } from \"./tools.ts\";\n\n/**\n * This module installs Istio 1.31.3 in a kind cluster with istioctl, from images pinned by digest.\n * The release notes of 1.31.2 and 1.31.3 change no default that a cell of T15 reads. Each value\n * of the install that is not a default of Istio is in `istioOperator`, with its reason.\n */\n\n/** This version is the release of Istio of the lab: the tag of istioctl and of each image. */\nexport const ISTIO_VERSION = \"1.31.3\";\n\n/**\n * These are the images of Istio, pinned by the digest of their index on Docker Hub (read on\n * 2026-10-24). `gcr.io/istio-release` has no final tag of 1.31. The lab pulls each one by its digest,\n * tags it with that digest (`localPin`), and loads it into each node. Thus no node pulls from a\n * registry. The flavor `sidecar` needs istiod and the proxy. The flavor `ambient` also needs\n * istio-cni and ztunnel (`MESH_IMAGES`).\n */\nexport const ISTIO_IMAGES: { readonly pilot: string; readonly proxy: string; readonly cni: string; readonly ztunnel: string } = {\n pilot: \"istio/pilot:1.31.3@sha256:6ad1e816c4d4767ba3f30a6286a96d121851ddb23d33e20736758acb5f3f4ea9\",\n proxy: \"istio/proxyv2:1.31.3@sha256:eb74eacea98d3fb1e239ea84016085155478586a554158676b9b67d960151084\",\n cni: \"istio/install-cni:1.31.3@sha256:e98782038634ef4fadaf78b53dd8fa4134fda3d65838ead5fc1edef245451c0f\",\n ztunnel: \"istio/ztunnel:1.31.3@sha256:2c27371b3139ecbf4f5a017354bd540889482441712e88cd8e654ead5a3eb5b2\",\n};\n\n/** These are the images that each flavor installs, by their names in `ISTIO_IMAGES`. */\nexport const MESH_IMAGES: Readonly<Record<MeshFlavor, ReadonlyArray<keyof typeof ISTIO_IMAGES>>> = {\n sidecar: [\"pilot\", \"proxy\"],\n ambient: [\"pilot\", \"proxy\", \"cni\", \"ztunnel\"],\n};\n\n/**\n * This format is the JSON access log of each sidecar. Each field is a command of the formatter of\n * Envoy v1.39.4. The fields give the status, the response flags and their details, and the gRPC\n * status as a number. They also give the tries of the router, the upstream host and cluster, and\n * the reasons of a failed connection. The watcher reads `code`, `flags`, `grpc` and `attempts`\n * (`image/watch.mjs`).\n */\nexport const ACCESS_LOG_FORMAT: Readonly<Record<string, string>> = {\n start: \"%START_TIME%\",\n code: \"%RESPONSE_CODE%\",\n flags: \"%RESPONSE_FLAGS%\",\n details: \"%RESPONSE_CODE_DETAILS%\",\n grpc: \"%GRPC_STATUS_NUMBER%\",\n attempts: \"%UPSTREAM_REQUEST_ATTEMPT_COUNT%\",\n host: \"%UPSTREAM_HOST%\",\n cluster: \"%UPSTREAM_CLUSTER%\",\n local: \"%DOWNSTREAM_LOCAL_ADDRESS%\",\n remote: \"%DOWNSTREAM_REMOTE_ADDRESS%\",\n duration: \"%DURATION%\",\n failure: \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\",\n termination: \"%CONNECTION_TERMINATION_DETAILS%\",\n};\n\n/** These are the local tags of the images, after the pull by digest. The flavor `ambient` needs `cni` and `ztunnel`. */\nexport interface MeshImages {\n readonly pilot: string;\n readonly proxy: string;\n readonly cni?: string | undefined;\n readonly ztunnel?: string | undefined;\n}\n\n/**\n * This function gives the IstioOperator of a flavor. The values that are not the defaults of Istio:\n * - The profile `minimal` is istiod only. The profile `default` also has an ingress gateway, which no cell uses.\n * - Each image is a full reference, thus the charts use it as it is (`contains \"/\"` in the\n * templates of istiod and of the injector). The init container of the injector has the image of the proxy too (`proxy_init`).\n * - `imagePullPolicy: Never`: each image is on each node. An image that the lab did not load fails the install at once.\n * - istiod requests 256 MiB, not 2 GiB, and has one replica with no autoscaler. Each node of kind\n * reports the whole runner, and no cell scales istiod.\n * - The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).\n *\n * The flavor `ambient` has the profile `ambient`: istiod, istio-cni and ztunnel, and no gateway\n * (manifests/profiles/ambient.yaml of Istio 1.31.3). Its two other images are full references too\n * (templates/daemonset.yaml of the chart of istio-cni, lines 93–96, and of ztunnel, lines 65–68).\n * The chart of istio-cni takes the pull policy of `global`. The template of ztunnel reads only its own\n * `imagePullPolicy`, thus the operator sets it there too.\n */\nexport const istioOperator = (flavor: MeshFlavor, images: MeshImages): Record<string, unknown> => {\n const ambient = flavor === \"ambient\";\n if (ambient && (images.cni === undefined || images.ztunnel === undefined)) throw new Error(\"the flavor ambient needs the images of istio-cni and ztunnel\");\n return {\n apiVersion: \"install.istio.io/v1alpha1\",\n kind: \"IstioOperator\",\n metadata: { name: \"lab\", namespace: \"istio-system\" },\n spec: {\n profile: ambient ? \"ambient\" : \"minimal\",\n hub: \"docker.io/istio\",\n tag: ISTIO_VERSION,\n meshConfig: {\n accessLogFile: \"/dev/stdout\",\n accessLogEncoding: \"JSON\",\n accessLogFormat: JSON.stringify(ACCESS_LOG_FORMAT),\n },\n values: {\n global: { imagePullPolicy: \"Never\", proxy: { image: images.proxy }, proxy_init: { image: images.proxy } },\n pilot: { image: images.pilot, autoscaleEnabled: false, replicaCount: 1, resources: { requests: { cpu: \"500m\", memory: \"256Mi\" } } },\n ...(ambient ? { cni: { image: images.cni }, ztunnel: { image: images.ztunnel, imagePullPolicy: \"Never\" } } : {}),\n },\n },\n };\n};\n\n/** These are the workloads of each flavor in istio-system that the install waits for. */\nexport const MESH_WORKLOADS: Readonly<Record<MeshFlavor, readonly string[]>> = {\n sidecar: [\"deployment/istiod\"],\n ambient: [\"deployment/istiod\", \"daemonset/istio-cni-node\", \"daemonset/ztunnel\"],\n};\n\n/**\n * This function installs the mesh of a flavor in a cluster with `istioctl install` and the operator\n * of the flavor. Then it waits for each workload of the flavor. The kubeconfig of the cluster goes to\n * istioctl. When the install fails, the error has the pods of istio-system and the last lines of the\n * log of istio-cni. Thus the log of the job shows the cause, for example a module that the kernel of\n * the runner does not have.\n */\nexport const installMesh = async (cluster: Cluster, flavor: MeshFlavor, images: MeshImages): Promise<void> => {\n const istioctl = await ensureTool(ISTIOCTL);\n const file = join(cluster.dir, `istio-${flavor}.json`);\n writeFileSync(file, `${JSON.stringify(istioOperator(flavor, images), null, 2)}\\n`);\n try {\n await command(istioctl, [\"install\", \"--kubeconfig\", cluster.kubeconfig, \"-y\", \"-f\", file, \"--readiness-timeout\", \"10m0s\"], { timeout: 900_000 });\n for (const workload of MESH_WORKLOADS[flavor]) await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", workload, \"--timeout=300s\"]);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", \"istio-system\", ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const cni = flavor === \"ambient\" ? `\\nthe log of istio-cni:\\n${await state([\"logs\", \"daemonset/istio-cni-node\", \"--tail=60\", \"--all-containers\"])}` : \"\";\n throw new Error(`the install of Istio ${flavor} failed: ${String(error)}\\nthe pods of istio-system:\\n${pods}${cni}`);\n }\n};\n\n/** This function gives the key of the mesh of a cell for its cluster: its flavor, or `none`. */\nexport const meshKey = (cell: Pick<KindCell, \"mesh\">): MeshFlavor | \"none\" => cell.mesh?.flavor ?? \"none\";\n\n/** This function gives the outbound cluster of the server of a cell in each client sidecar: the port 80 of its Service. */\nexport const serverCluster = (namespace: string): string => outboundCluster(SERVER, namespace, 80);\n\n/** This time is the interval of the EDS poll of each client sidecar, the same as the poll of the rules. */\nexport const EDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** This time is the interval of the LDS poll of each server sidecar, the same as the poll of EDS. */\nexport const LDS_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/**\n * This function tells if the watcher follows each ztunnel pod and its log for a cell: a cell in the\n * flavor `ambient`. A cell of sidecar pods in that flavor needs the lines of the ztunnel pods too.\n * They show the restart of ztunnel, which its pods do not see.\n */\nexport const followsZtunnel = (cell: Pick<KindCell, \"mesh\">): boolean => cell.mesh?.flavor === \"ambient\";\n\n/**\n * This function gives the arguments of kubectl that set the environment of ztunnel for a cell, or\n * remove it (`undo`). The variables go in the sequence of their names.\n */\nexport const ztunnelEnvArgs = (env: Readonly<Record<string, string>>, undo: boolean): string[] => [\n \"-n\",\n \"istio-system\",\n \"set\",\n \"env\",\n \"daemonset/ztunnel\",\n ...Object.keys(env)\n .sort()\n .map((key) => (undo ? `${key}-` : `${key}=${env[key]!}`)),\n];\n\n/**\n * This function sets the environment of ztunnel for a cell, or removes it (`undo`). Then it waits for\n * the rollout of the DaemonSet, and until no old ztunnel pod is left. An old pod drains for its\n * deadline after the rollout replaces it. Thus no run starts beside a ztunnel pod that is going.\n */\nexport const setZtunnelEnv = async (cluster: Cluster, env: Readonly<Record<string, string>>, undo: boolean): Promise<void> => {\n await cluster.kubectl(ztunnelEnvArgs(env, undo));\n await cluster.kubectl([\"-n\", \"istio-system\", \"rollout\", \"status\", \"daemonset/ztunnel\", \"--timeout=300s\"]);\n const until = Date.now() + 120_000;\n for (;;) {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", \"istio-system\", \"get\", \"pods\", \"-l\", ZTUNNEL_PODS, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { deletionTimestamp?: string } }>;\n };\n if (listed.items.every((item) => item.metadata.deletionTimestamp === undefined)) return;\n if (Date.now() > until) throw new Error(\"an old ztunnel pod is still going 120 s after the rollout\");\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n};\n\n/** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */\nexport const pollsListeners = (cell: Pick<KindCell, \"mesh\" | \"trigger\">): boolean => cell.mesh?.mode === \"sidecar\" && cell.trigger.kind === \"authorization-policy\";\n\n/** This is the validity of one leaf certificate of ztunnel, as istioctl gives it: RFC 3339 times. */\nexport interface LeafCertificate {\n readonly pod: string;\n readonly identity: string;\n readonly state: string;\n readonly from: string | null;\n readonly to: string | null;\n}\n\n/** This function gives the arguments of istioctl that read the certificates of one ztunnel pod as JSON. */\nexport const certificateArgs = (pod: string, kubeconfig: string): string[] => [\n \"ztunnel-config\",\n \"certificate\",\n `${pod}.istio-system`,\n \"--kubeconfig\",\n kubeconfig,\n \"-o\",\n \"json\",\n];\n\n/**\n * This function gives the leaf of each identity in the JSON of `istioctl ztunnel-config certificate`.\n * The leaf is the first certificate of the chain (certificates.go of istioctl 1.31.3, lines 68–71).\n * An identity with no chain yet has no times.\n */\nexport const leafCertificates = (pod: string, json: string): LeafCertificate[] => {\n const dump = JSON.parse(json) as Array<{\n identity?: string;\n state?: string;\n certChain?: Array<{ validFrom?: string; expirationTime?: string }> | null;\n }>;\n return dump.map((each) => {\n const leaf = each.certChain?.[0];\n return { pod, identity: each.identity ?? \"\", state: each.state ?? \"\", from: leaf?.validFrom || null, to: leaf?.expirationTime || null };\n });\n};\n\n/**\n * This function reads the leaf certificates of each ztunnel pod that is not going, with istioctl.\n * The CLI reads the admin port of ztunnel through a port-forward. Each ztunnel binds that port to\n * localhost only (config.rs of ztunnel 1.31.3, lines 831–835).\n */\nexport const ztunnelCertificates = async (cluster: Cluster): Promise<LeafCertificate[]> => {\n const istioctl = await ensureTool(ISTIOCTL);\n const listed = JSON.parse(await cluster.kubectl([\"-n\", \"istio-system\", \"get\", \"pods\", \"-l\", ZTUNNEL_PODS, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string; deletionTimestamp?: string } }>;\n };\n const out: LeafCertificate[] = [];\n for (const item of listed.items.filter((each) => each.metadata.deletionTimestamp === undefined)) {\n const json = await command(istioctl, certificateArgs(item.metadata.name, cluster.kubeconfig), { timeout: 60_000 });\n out.push(...leafCertificates(item.metadata.name, json));\n }\n return out;\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { cidrRange } from \"./scenario.ts\";\n\n/**\n * This module installs kube-vip 1.3.1 in a kind cluster (SYS-51). The install gives a Service of type\n * LoadBalancer an address on the Docker network of the nodes: the VIP. In ARP mode, one node holds\n * the VIP on its link, and it sends a gratuitous ARP for it each `vip_arpRate`. A Lease elects that\n * node. Each value of the install is in `kubeVipObjects`, with its reason.\n *\n * The lab uses a VIP of a Service, not a VIP of the control plane. A VIP of the control plane needs\n * three control-plane nodes and TLS on port 6443. A VIP of a Service needs one DaemonSet on the\n * workers, and it serves the server of the cell.\n *\n * Each term of the election comes from the source of v1.3.1:\n * - With `svc_election` off, one election of the lease `plndr-svcs-lock` gives each VIP to one\n * node (pkg/manager/worker/common.go, `GlobalLeader`). The lease is in the namespace of the pod.\n * - The election is `leaderelection.RunOrDie` of client-go v0.37.1, with a Lease lock and\n * `ReleaseOnCancel` (pkg/election/election.go). The identity is the name of the node.\n * - A leader that loses its lease stops the process (`OnStoppedLeading`), and the kubelet starts it again.\n * - The ARP manager sends a gratuitous ARP for each VIP at each tick of one ticker of\n * `vip_arpRate`. A new VIP gets its first one at the next tick, not at once (pkg/arp/arp.go).\n */\n\n/** This version is the release of kube-vip of the lab, from 2026-10-19: the tag of its image. */\nexport const KUBE_VIP_VERSION = \"1.3.1\";\n\n/**\n * This image is kube-vip 1.3.1 for linux/amd64, pinned by the digest of its manifest. The digest of\n * the index of the tag is sha256:1962f9daf226503f7e6b107f595cb28b7917fd2d0113957075e19a3e5a6c1e33.\n * The manifest of one platform loads into kind with no index.\n */\nexport const KUBE_VIP_IMAGE = \"ghcr.io/kube-vip/kube-vip:v1.3.1@sha256:47353c7bde7fec21642cf621bc3fda2b999c051342912efda468999a399be667\";\n\n/** This is the name of kube-vip: of its ServiceAccount, its DaemonSet and its container. */\nexport const KUBE_VIP = \"kube-vip\";\n\n/** This is the Lease of the election of the VIPs of the Services (`svc_leasename`, cmd/kube-vip.go of v1.3.1). */\nexport const KUBE_VIP_LEASE: { readonly namespace: string; readonly name: string } = { namespace: \"kube-system\", name: \"plndr-svcs-lock\" };\n\n/**\n * These are the times of the election, as the install sets them. Each one is the default of the\n * flags of v1.3.1 (cmd/kube-vip.go, lines 91–94). They are a lease of 15 s, a renew deadline of 10 s\n * and a retry period of 2 s. The install sets each one in the environment, thus a new default cannot\n * change a cell.\n */\nexport const KUBE_VIP_ELECTION: { readonly lease: Quantity; readonly renew: Quantity; readonly retry: Quantity } = {\n lease: { value: 15, unit: \"s\" },\n renew: { value: 10, unit: \"s\" },\n retry: { value: 2, unit: \"s\" },\n};\n\n/** This time is the interval of the gratuitous ARPs, the default of the flag `arpRate` of v1.3.1 (cmd/kube-vip.go, line 61). */\nexport const KUBE_VIP_ARP_RATE: Quantity = { value: 3000, unit: \"ms\" };\n\n/** This label of a node selects the nodes of kube-vip: the DaemonSet has a pod only on a node with it. */\nexport const VIP_LABEL = \"lab.systems/vip\";\n\n/** This label of a pod of kube-vip selects them. */\nconst APP = { \"app.kubernetes.io/name\": KUBE_VIP };\n\nconst whole = (amount: Quantity): string => String(Math.round(valueIn(amount, \"s\")));\n\n/**\n * This function gives the objects of kube-vip in kube-system. They are its ServiceAccount, its\n * ClusterRole and binding, and its DaemonSet. The rules of the role are those of the manifest that\n * v1.3.1 generates (pkg/kubevip/config_generator.go, lines 95–134). The DaemonSet:\n * - is on the host network, with NET_ADMIN and NET_RAW. It adds the VIP to `eth0` and sends ARP.\n * - has a pod only on a node with `VIP_LABEL`. Thus the lab chooses the holder of the VIP.\n * - sets ARP mode, the Services, one election for all VIPs, and each time of the election.\n * - takes the image from the node (`imagePullPolicy: Never`).\n */\nexport const kubeVipObjects = (image: string): unknown[] => {\n const env: Record<string, string> = {\n vip_arp: \"true\",\n vip_interface: \"eth0\",\n port: \"6443\",\n cp_enable: \"false\",\n svc_enable: \"true\",\n svc_election: \"false\",\n svc_leasename: KUBE_VIP_LEASE.name,\n vip_leaderelection: \"true\",\n vip_leaseduration: whole(KUBE_VIP_ELECTION.lease),\n vip_renewdeadline: whole(KUBE_VIP_ELECTION.renew),\n vip_retryperiod: whole(KUBE_VIP_ELECTION.retry),\n vip_arpRate: String(Math.round(valueIn(KUBE_VIP_ARP_RATE, \"ms\"))),\n };\n const namespace = KUBE_VIP_LEASE.namespace;\n return [\n { apiVersion: \"v1\", kind: \"ServiceAccount\", metadata: { name: KUBE_VIP, namespace } },\n {\n apiVersion: \"rbac.authorization.k8s.io/v1\",\n kind: \"ClusterRole\",\n metadata: { name: \"system:kube-vip-role\" },\n rules: [\n { apiGroups: [\"\"], resources: [\"services/status\"], verbs: [\"update\"] },\n { apiGroups: [\"\"], resources: [\"services\", \"endpoints\"], verbs: [\"list\", \"get\", \"watch\", \"update\"] },\n { apiGroups: [\"\"], resources: [\"nodes\"], verbs: [\"list\", \"get\", \"watch\", \"update\", \"patch\"] },\n { apiGroups: [\"coordination.k8s.io\"], resources: [\"leases\"], verbs: [\"list\", \"get\", \"watch\", \"update\", \"create\"] },\n { apiGroups: [\"discovery.k8s.io\"], resources: [\"endpointslices\"], verbs: [\"list\", \"get\", \"watch\", \"update\"] },\n { apiGroups: [\"\"], resources: [\"pods\"], verbs: [\"list\"] },\n { apiGroups: [\"networking.k8s.io\"], resources: [\"servicecidrs\"], verbs: [\"list\", \"get\", \"watch\"] },\n ],\n },\n {\n apiVersion: \"rbac.authorization.k8s.io/v1\",\n kind: \"ClusterRoleBinding\",\n metadata: { name: \"system:kube-vip-binding\" },\n roleRef: { apiGroup: \"rbac.authorization.k8s.io\", kind: \"ClusterRole\", name: \"system:kube-vip-role\" },\n subjects: [{ kind: \"ServiceAccount\", name: KUBE_VIP, namespace }],\n },\n {\n apiVersion: \"apps/v1\",\n kind: \"DaemonSet\",\n metadata: { name: KUBE_VIP, namespace, labels: APP },\n spec: {\n selector: { matchLabels: APP },\n template: {\n metadata: { labels: APP },\n spec: {\n serviceAccountName: KUBE_VIP,\n hostNetwork: true,\n nodeSelector: { [VIP_LABEL]: \"1\" },\n containers: [\n {\n name: KUBE_VIP,\n image,\n imagePullPolicy: \"Never\",\n args: [\"manager\"],\n env: [\n ...Object.entries(env).map(([name, value]) => ({ name, value })),\n { name: \"vip_nodename\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n securityContext: { capabilities: { add: [\"NET_ADMIN\", \"NET_RAW\"] } },\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/** This is the Lease of the election, as the API gives it: its holder and its times. */\nexport interface VipLease {\n readonly holder: string | null;\n readonly acquireTime: string | null;\n readonly renewTime: string | null;\n readonly leaseDurationSeconds: number | null;\n readonly leaseTransitions: number | null;\n}\n\n/** This function gives the Lease of the election, or null when it is not there yet. */\nexport const vipLeaseOf = async (cluster: Pick<Cluster, \"kubectl\">): Promise<VipLease | null> => {\n const text = await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"get\", \"lease\", KUBE_VIP_LEASE.name, \"-o\", \"json\", \"--ignore-not-found\"]);\n if (text.trim() === \"\") return null;\n const spec = (JSON.parse(text) as { spec?: Record<string, unknown> }).spec ?? {};\n const text0 = (key: string): string | null => (typeof spec[key] === \"string\" && spec[key] !== \"\" ? (spec[key] as string) : null);\n const number0 = (key: string): number | null => (typeof spec[key] === \"number\" ? (spec[key] as number) : null);\n return {\n holder: text0(\"holderIdentity\"),\n acquireTime: text0(\"acquireTime\"),\n renewTime: text0(\"renewTime\"),\n leaseDurationSeconds: number0(\"leaseDurationSeconds\"),\n leaseTransitions: number0(\"leaseTransitions\"),\n };\n};\n\n/** This is the Docker network of a cluster: its IPv4 subnet, and the MAC address of each container on it, by name. */\nexport interface VipNetwork {\n readonly subnet: string;\n readonly macs: Readonly<Record<string, string>>;\n}\n\n/** This function reads the Docker network of a cluster: its IPv4 subnet, and the MAC address of each container. */\nexport const vipNetworkOf = async (network: string): Promise<VipNetwork> => {\n const [info] = JSON.parse(await command(\"docker\", [\"network\", \"inspect\", network])) as Array<{\n IPAM?: { Config?: Array<{ Subnet?: string }> | null };\n Containers?: Record<string, { Name?: string; MacAddress?: string }> | null;\n }>;\n const subnet = (info?.IPAM?.Config ?? []).map((config) => config.Subnet ?? \"\").find((each) => /^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$/.test(each));\n if (subnet === undefined) throw new Error(`the Docker network ${network} has no IPv4 subnet`);\n const macs: Record<string, string> = {};\n for (const container of Object.values(info?.Containers ?? {})) if (container.Name !== undefined && container.MacAddress !== undefined) macs[container.Name] = container.MacAddress.toLowerCase();\n return { subnet, macs };\n};\n\n/**\n * This function gives the VIP of a subnet: the sixth address from its end. Docker gives the address\n * of each container from the start of the subnet, thus no container takes it.\n */\nexport const vipOf = (subnet: string): string => {\n const [first, last] = cidrRange(subnet);\n const address = last - 5;\n if (address <= first + 1) throw new Error(`the subnet ${subnet} is too small for a VIP`);\n return [24, 16, 8, 0].map((shift) => Math.floor(address / 2 ** shift) % 256).join(\".\");\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\ninterface PodList {\n readonly items: ReadonlyArray<{\n readonly metadata: { readonly name: string; readonly deletionTimestamp?: string };\n readonly spec: { readonly nodeName?: string };\n readonly status?: { readonly containerStatuses?: ReadonlyArray<{ readonly ready: boolean; readonly restartCount?: number }> };\n }>;\n}\n\n/** This is a pod of kube-vip that is not deleted: its name, its node, if it is ready, and the restarts of its container. */\nexport interface KubeVipPod {\n readonly name: string;\n readonly node: string;\n readonly ready: boolean;\n readonly restarts: number;\n}\n\n/** This function gives the pods of kube-vip that are not deleted. */\nexport const kubeVipPods = async (cluster: Pick<Cluster, \"kubectl\">): Promise<KubeVipPod[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"get\", \"pods\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"-o\", \"json\"])) as PodList;\n return list.items\n .filter((pod) => pod.metadata.deletionTimestamp === undefined)\n .map((pod) => {\n const statuses = pod.status?.containerStatuses ?? [];\n return {\n name: pod.metadata.name,\n node: pod.spec.nodeName ?? \"\",\n ready: statuses.length > 0 && statuses.every((status) => status.ready),\n restarts: statuses.reduce((sum, status) => sum + (status.restartCount ?? 0), 0),\n };\n });\n};\n\n/** This function gives the nodes with a ready pod of kube-vip that is not deleted. */\nexport const kubeVipReady = async (cluster: Pick<Cluster, \"kubectl\">): Promise<string[]> =>\n (await kubeVipPods(cluster))\n .filter((pod) => pod.ready && pod.node !== \"\")\n .map((pod) => pod.node)\n .sort();\n\n/** This function waits until a condition gives null, and gives null, or the last reason after `within` ms. */\nconst until = async (within: number, check: () => Promise<string | null>): Promise<string | null> => {\n const end = Date.now() + within;\n let why = await check();\n while (why !== null && Date.now() < end) {\n await sleep(500);\n why = await check();\n }\n return why;\n};\n\nconst label = (cluster: Pick<Cluster, \"kubectl\">, node: string, on: boolean): Promise<string> =>\n cluster.kubectl([\"label\", \"node\", node, on ? `${VIP_LABEL}=1` : `${VIP_LABEL}-`, ...(on ? [\"--overwrite\"] : [])]);\n\n/**\n * This function makes `holder` the holder of the VIP, with a ready pod of kube-vip on each node of\n * `nodes`. It gives null, or why it could not in `within` ms. If another node holds the lease, the\n * function takes the label from each other node. The DaemonSet then deletes their pods. The leader\n * gives back its lease at its stop (`ReleaseOnCancel`). The function gives each label back after\n * `holder` takes the lease.\n *\n * A leader that lost its lease restarted its container. The kubelet gives a container a longer\n * back-off at each restart, thus the function first deletes each pod of kube-vip with a restart.\n * The DaemonSet then makes a new pod with no back-off.\n */\nexport const holdVip = async (cluster: Pick<Cluster, \"kubectl\">, holder: string, nodes: readonly string[], within = 180_000): Promise<string | null> => {\n const others = nodes.filter((node) => node !== holder);\n for (const pod of await kubeVipPods(cluster))\n if (pod.restarts > 0) await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"delete\", \"pod\", pod.name, \"--wait=true\", \"--timeout=120s\"]);\n await label(cluster, holder, true);\n const own = await until(within, async () => ((await kubeVipReady(cluster)).includes(holder) ? null : `kube-vip on ${holder} is not ready`));\n if (own !== null) return own;\n const held = async (): Promise<string | null> => {\n const lease = await vipLeaseOf(cluster);\n return lease?.holder === holder ? null : `the lease ${KUBE_VIP_LEASE.name} is held by ${lease?.holder ?? \"no node\"}, not ${holder}`;\n };\n if ((await held()) !== null) {\n for (const node of others) await label(cluster, node, false);\n const moved = await until(within, held);\n if (moved !== null) return moved;\n }\n for (const node of others) await label(cluster, node, true);\n return until(within, async () => {\n const ready = await kubeVipReady(cluster);\n const missing = nodes.filter((node) => !ready.includes(node));\n return missing.length === 0 ? null : `kube-vip is not ready on ${missing.join(\", \")}`;\n });\n};\n\n/**\n * This function installs kube-vip in a cluster, with its image on each node (`cluster.load`), and\n * makes `holder` the holder of the VIP. A failed install gives the pods of kube-vip and the tail of\n * their logs.\n */\nexport const installKubeVip = async (cluster: Cluster, image: string, holder: string, nodes: readonly string[]): Promise<void> => {\n try {\n await label(cluster, holder, true);\n await cluster.apply(kubeVipObjects(image));\n const why = await holdVip(cluster, holder, nodes);\n if (why !== null) throw new Error(why);\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", KUBE_VIP_LEASE.namespace, ...args]).catch((failure: unknown) => String(failure))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"-o\", \"wide\"]);\n const logs = await state([\"logs\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"--tail=40\", \"--prefix\"]);\n throw new Error(`the install of kube-vip failed: ${String(error)}\\nthe pods of kube-vip:\\n${pods}\\nthe logs:\\n${logs}`);\n }\n};\n\n/**\n * This function waits until the Service of a VIP has the VIP in its status: kube-vip took it. It\n * gives null, or why not in `within` ms.\n */\nexport const vipServed = (cluster: Pick<Cluster, \"kubectl\">, namespace: string, service: string, vip: string, within = 120_000): Promise<string | null> =>\n until(within, async () => {\n const ingress = (await cluster.kubectl([\"-n\", namespace, \"get\", \"service\", service, \"-o\", \"jsonpath={.status.loadBalancer.ingress[*].ip}\"])).trim();\n return ingress.split(/\\s+/).includes(vip) ? null : `the Service ${service} has the ingress ${ingress || \"none\"}, not ${vip}`;\n });\n","import { createHash } from \"node:crypto\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { MemberlistSettings } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport type { PinnedChart } from \"./helm.ts\";\nimport { helmInstall } from \"./helm.ts\";\nimport { VIP_LABEL } from \"./kubevip.ts\";\nimport { cidrRange } from \"./scenario.ts\";\n\n/**\n * This module installs MetalLB 0.16.1 in L2 mode in a kind cluster (SYS-53). The controller of\n * MetalLB gives a Service of type LoadBalancer an address from a pool: the VIP. A speaker on each\n * worker answers ARP for the VIP if its node owns the VIP. The VIP is on no interface of a node.\n * Thus a frozen speaker answers nothing.\n *\n * Each term of the owner comes from the source of the tag v0.16.1 (commit 791dfcc6):\n * - The owner of a VIP is the first node in the order of sha256(node + \"#\" + VIP), of the nodes with\n * a usable speaker (speaker/layer2_controller.go, lines 83–129). A usable speaker is a member of\n * memberlist that is not dead (internal/speakerlist/speakerlist.go, lines 303–320).\n * - The speakers start memberlist from `DefaultLANConfig` (speakerlist.go, line 72). A file\n * /etc/metallb/memberlist/config.json replaces that config (lines 77–84). Without settings of\n * memberlist, the install makes no such file. Then each value is the default of memberlist v0.5.1.\n * - Each event of memberlist (a join, a leave or an update) calls `ForceSync` (speakerlist.go, lines\n * 339–349). `ForceSync` reconciles each Service at once, with no delay (internal/k8s/k8s.go, lines\n * 181–184, and internal/k8s/controllers/service_controller_reload.go, lines 62–118).\n * - The owner sends a gratuitous ARP at each reconcile. It sends one at once, then one each 1100 ms\n * for 5 s (internal/layer2/announcer.go, lines 157–197 and 258–272).\n */\n\n/** This version is the release of MetalLB of the lab: the tag v0.16.1, the commit 791dfcc63ced7a6b3e869131eae75e645150fff0. */\nexport const METALLB_VERSION = \"0.16.1\";\n\n/** This chart is the Helm chart of MetalLB 0.16.1. The sha256 is of the file of the release, and it is the digest in the index of the charts. */\nexport const METALLB_CHART: PinnedChart = {\n name: \"metallb\",\n version: METALLB_VERSION,\n url: \"https://github.com/metallb/metallb/releases/download/metallb-chart-0.16.1/metallb-0.16.1.tgz\",\n sha256: \"fb06bb584fcb7856f15733b2a6a2aff5b61b5c350687e341c163ae24a5938adc\",\n};\n\n/**\n * These are the images of MetalLB 0.16.1 for linux/amd64, pinned by the digest of the manifest of the\n * platform. The lab pulls each one by its digest, tags it (`localPin`), and loads it into each node.\n */\nexport const METALLB_IMAGES: { readonly speaker: string; readonly controller: string } = {\n speaker: \"quay.io/metallb/speaker:v0.16.1@sha256:37a98a9d1cd970051c5dededb6f922c1e6c3b90fdd0fc1350d1686e67675af0e\",\n controller: \"quay.io/metallb/controller:v0.16.1@sha256:5a3e101335f5ea2cfb0ddf51acdfa9538251e0623ffcd6cbfc1b274b7898790c\",\n};\n\n/** These are the local tags of the images of MetalLB, after the pull by digest. */\nexport interface MetallbImages {\n readonly speaker: string;\n readonly controller: string;\n}\n\n/** This namespace holds MetalLB: its controller, its speakers and its objects. */\nexport const METALLB_NAMESPACE = \"metallb-system\";\n\n/** This annotation of a Service gives the controller of MetalLB the address of its VIP. */\nexport const METALLB_ANNOTATION = \"metallb.io/loadBalancerIPs\";\n\n/** This name is the name of the pool of the VIP and of its L2Advertisement. */\nexport const METALLB_POOL = \"lab-vip\";\n\n/** This annotation of the Service makes each speaker reconcile it. The owner then sends its gratuitous ARPs. */\nexport const ANNOUNCE_ANNOTATION = \"lab.systems/announce\";\n\n/**\n * These are the terms of memberlist v0.5.1 that the speakers use: the defaults of `DefaultLANConfig`\n * (config.go, lines 302–341).\n * - `probeInterval` is the interval of the probes (line 318). The awareness of a healthy member does not scale it.\n * - `probeTimeout` is the wait for the ack of a direct ping (line 317).\n * - `suspicionMult` and `suspicionMaxTimeoutMult` give the time from suspect to dead (lines 314–315).\n * - `pushPullInterval` is the interval of the full exchange of state (line 316).\n * - `gossipInterval` is the interval of the gossip (line 323).\n */\nexport const MEMBERLIST: {\n readonly probeInterval: Quantity;\n readonly probeTimeout: Quantity;\n readonly suspicionMult: number;\n readonly suspicionMaxTimeoutMult: number;\n readonly pushPullInterval: Quantity;\n readonly gossipInterval: Quantity;\n} = {\n probeInterval: { value: 1, unit: \"s\" },\n probeTimeout: { value: 500, unit: \"ms\" },\n suspicionMult: 4,\n suspicionMaxTimeoutMult: 6,\n pushPullInterval: { value: 30, unit: \"s\" },\n gossipInterval: { value: 200, unit: \"ms\" },\n};\n\n/**\n * These are the values of the config file of memberlist, as `DefaultLANConfig` of v0.5.1 gives them\n * (config.go, lines 302–341). The speaker needs each field of the file, and it reads a time as a\n * duration of Go (speakerlist.go, lines 355–432). The chart mounts the ConfigMap\n * `metallb-memberlist-config` at /etc/metallb/memberlist, if the ConfigMap exists\n * (charts/metallb/templates/speaker.yaml, lines 195–198 and 391–392). The key is `config.json`.\n */\nexport const MEMBERLIST_FILE: Readonly<Record<string, string | number | boolean>> = {\n TCPTimeout: \"10s\",\n IndirectChecks: 3,\n RetransmitMult: 4,\n SuspicionMult: 4,\n SuspicionMaxTimeoutMult: 6,\n PushPullInterval: \"30s\",\n ProbeTimeout: \"500ms\",\n ProbeInterval: \"1s\",\n DisableTcpPings: false,\n AwarenessMaxMultiplier: 8,\n GossipNodes: 3,\n GossipInterval: \"200ms\",\n GossipToTheDeadTime: \"30s\",\n GossipVerifyIncoming: true,\n GossipVerifyOutgoing: true,\n EnableCompression: true,\n HandoffQueueDepth: 1024,\n UDPBufferSize: 1400,\n QueueCheckInterval: \"30s\",\n};\n\n/** This name is the ConfigMap of the config file of memberlist. */\nconst MEMBERLIST_CONFIG = \"metallb-memberlist-config\";\n\n/** This function gives a time as a duration of Go in whole milliseconds, for example \"500ms\". */\nconst goDuration = (time: Quantity): string => {\n const ms = valueIn(time, \"ms\");\n if (!Number.isInteger(ms) || ms <= 0) throw new RangeError(`a time of memberlist is a whole count of milliseconds above zero, not ${ms} ms`);\n return `${ms}ms`;\n};\n\n/** This function gives the config file of memberlist with `settings`. Each other value is the default of `MEMBERLIST_FILE`. */\nexport const memberlistFileOf = (settings: MemberlistSettings): Record<string, string | number | boolean> => {\n if (!Number.isInteger(settings.suspicionMult) || settings.suspicionMult < 1) throw new RangeError(`SuspicionMult is a whole number of 1 or more, not ${settings.suspicionMult}`);\n if (valueIn(settings.probeTimeout, \"ms\") >= valueIn(settings.probeInterval, \"ms\")) throw new RangeError(\"the probe timeout of memberlist is less than its probe interval\");\n return { ...MEMBERLIST_FILE, ProbeInterval: goDuration(settings.probeInterval), ProbeTimeout: goDuration(settings.probeTimeout), SuspicionMult: settings.suspicionMult };\n};\n\n/** This function gives the terms of memberlist with `settings`, or the defaults (`MEMBERLIST`) without them. */\nexport const memberlistTermsOf = (settings: MemberlistSettings | undefined): typeof MEMBERLIST =>\n settings === undefined ? MEMBERLIST : { ...MEMBERLIST, probeInterval: settings.probeInterval, probeTimeout: settings.probeTimeout, suspicionMult: settings.suspicionMult };\n\n/** These are the gratuitous ARPs of the owner at each reconcile: one at once, then one each `every` until `for` (announcer.go, lines 172–182). */\nexport const METALLB_SPAM: { readonly every: Quantity; readonly for: Quantity } = {\n every: { value: 1100, unit: \"ms\" },\n for: { value: 5, unit: \"s\" },\n};\n\n/**\n * This function gives the time from suspect to dead in memberlist v0.5.1, for a cluster of `members`\n * members with no peer that can confirm. The terms:\n * - `suspicionTimeout` is SuspicionMult × max(1, log10(n)) × ProbeInterval (util.go, lines 71–76).\n * - `suspectNode` sets k = SuspicionMult − 2 confirmations, and k = 0 if n − 2 < k (state.go, lines 1216–1224).\n * - With k = 0, the timer fires at the minimum, not at the maximum (suspicion.go, lines 70–76).\n * With confirmations to wait for, the function gives the maximum: SuspicionMaxTimeoutMult times the minimum.\n */\nexport const suspicionOf = (members: number, terms: Pick<typeof MEMBERLIST, \"probeInterval\" | \"suspicionMult\" | \"suspicionMaxTimeoutMult\"> = MEMBERLIST): Quantity => {\n const scale = Math.max(1, Math.log10(Math.max(1, members)));\n const min = Math.floor(scale * 1000) * valueIn(terms.probeInterval, \"ms\") * terms.suspicionMult / 1000;\n const k = members - 2 < terms.suspicionMult - 2 ? 0 : terms.suspicionMult - 2;\n return { value: k < 1 ? min : min * terms.suspicionMaxTimeoutMult, unit: \"ms\" };\n};\n\n/**\n * This function gives the Helm values of the install. The values that are not the defaults of the chart:\n * - No FRR and no frr-k8s: the lab uses L2 mode only, thus it needs no BGP.\n * - A speaker only on a node with `VIP_LABEL`, the workers. Thus memberlist has the workers as its\n * members, and the control plane does not answer ARP.\n * - The controller on the control plane. A zone loss of a worker then does not take it away.\n * - Each image from the node (`pullPolicy: Never`).\n */\nexport const metallbValues = (images: MetallbImages): Record<string, unknown> => {\n const image = (local: string) => {\n const at = local.lastIndexOf(\":\");\n if (at <= 0) throw new Error(`the image ${local} has no tag`);\n return { repository: local.slice(0, at), tag: local.slice(at + 1), pullPolicy: \"Never\" };\n };\n return {\n frrk8s: { enabled: false },\n controller: {\n image: image(images.controller),\n nodeSelector: { \"node-role.kubernetes.io/control-plane\": \"\" },\n tolerations: [{ key: \"node-role.kubernetes.io/control-plane\", operator: \"Exists\", effect: \"NoSchedule\" }],\n },\n speaker: {\n image: image(images.speaker),\n frr: { enabled: false },\n tolerateMaster: false,\n nodeSelector: { [VIP_LABEL]: \"1\" },\n memberlist: { enabled: true, mlBindPort: 7946 },\n },\n };\n};\n\n/** This function gives the pool of the VIP and its L2Advertisement. The pool has only the VIP. */\nexport const metallbObjects = (vip: string): unknown[] => [\n {\n apiVersion: \"metallb.io/v1beta1\",\n kind: \"IPAddressPool\",\n metadata: { name: METALLB_POOL, namespace: METALLB_NAMESPACE },\n spec: { addresses: [`${vip}/32`] },\n },\n {\n apiVersion: \"metallb.io/v1beta1\",\n kind: \"L2Advertisement\",\n metadata: { name: METALLB_POOL, namespace: METALLB_NAMESPACE },\n spec: { ipAddressPools: [METALLB_POOL] },\n },\n];\n\n/** This function gives the nodes in the order of the election of the owner of `vip`: by sha256(node + \"#\" + vip), as bytes (layer2_controller.go, lines 115–120). */\nexport const ownerOrder = (nodes: readonly string[], vip: string): string[] => {\n const hash = (node: string) => createHash(\"sha256\").update(`${node}#${vip}`).digest();\n return [...nodes].sort((a, b) => Buffer.compare(hash(a), hash(b)));\n};\n\nconst dotted = (address: number): string => [24, 16, 8, 0].map((shift) => Math.floor(address / 2 ** shift) % 256).join(\".\");\n\n/**\n * This function gives the VIP of a subnet for MetalLB. It looks down from the sixth address from the\n * end, and it gives the first address that `owner` owns, of the nodes `nodes`. Docker gives the address of each container\n * from the start of the subnet, thus no container takes it.\n */\nexport const metallbVipOf = (subnet: string, owner: string, nodes: readonly string[]): string => {\n if (!nodes.includes(owner)) throw new Error(`the owner ${owner} is not one of the nodes ${nodes.join(\", \")}`);\n const [first, last] = cidrRange(subnet);\n for (let address = last - 5; address > first + 1 && address > last - 260; address--) {\n const vip = dotted(address);\n if (ownerOrder(nodes, vip)[0] === owner) return vip;\n }\n throw new Error(`the subnet ${subnet} has no VIP that ${owner} owns`);\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/** This function waits until a condition gives null, and gives null, or the last reason after `within` ms. */\nconst until = async (within: number, check: () => Promise<string | null>): Promise<string | null> => {\n const end = Date.now() + within;\n let why = await check();\n while (why !== null && Date.now() < end) {\n await sleep(500);\n why = await check();\n }\n return why;\n};\n\n/** This is a ServiceL2Status of MetalLB: the node that announces a Service, and the time of the object. */\nexport interface Announcement {\n readonly node: string;\n readonly created: string | null;\n}\n\n/** This function gives the nodes that announce a Service, from the ServiceL2Status objects of the speakers. */\nexport const announcementsOf = async (cluster: Pick<Cluster, \"kubectl\">, namespace: string, service: string): Promise<Announcement[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"servicel2statuses.metallb.io\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{ metadata?: { creationTimestamp?: string }; status?: { node?: string; serviceName?: string; serviceNamespace?: string } }>;\n };\n return list.items\n .filter((item) => item.status?.serviceName === service && item.status.serviceNamespace === namespace && typeof item.status.node === \"string\")\n .map((item) => ({ node: item.status!.node!, created: item.metadata?.creationTimestamp ?? null }))\n .sort((a, b) => a.node.localeCompare(b.node));\n};\n\n/** This function gives the nodes with a ready speaker that is not deleted. */\nexport const speakersReady = async (cluster: Pick<Cluster, \"kubectl\">): Promise<string[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"pods\", \"-l\", \"app.kubernetes.io/component=speaker\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{ metadata: { deletionTimestamp?: string }; spec: { nodeName?: string }; status?: { containerStatuses?: ReadonlyArray<{ ready: boolean }> } }>;\n };\n return list.items\n .filter((pod) => pod.metadata.deletionTimestamp === undefined && pod.spec.nodeName !== undefined)\n .filter((pod) => (pod.status?.containerStatuses ?? []).length > 0 && pod.status!.containerStatuses!.every((status) => status.ready))\n .map((pod) => pod.spec.nodeName!)\n .sort();\n};\n\n/**\n * This function makes `owner` the only node that announces the Service, with a ready speaker on each\n * node of `nodes`. It gives null, or why it could not in `within` ms. After a heal, the other node\n * stops when it sees that the owner is alive again. The function then annotates the Service. Each\n * speaker reconciles it, and the owner sends its gratuitous ARPs for 5 s. Thus each client learns\n * the MAC address of the owner.\n */\nexport const holdMetallb = async (cluster: Pick<Cluster, \"kubectl\">, owner: string, nodes: readonly string[], namespace: string, service: string, within = 180_000): Promise<string | null> => {\n const ready = await until(within, async () => {\n const found = await speakersReady(cluster);\n const missing = nodes.filter((node) => !found.includes(node));\n return missing.length === 0 ? null : `the speaker of MetalLB is not ready on ${missing.join(\", \")}`;\n });\n if (ready !== null) return ready;\n const sole = await until(within, async () => {\n const found = (await announcementsOf(cluster, namespace, service)).map((each) => each.node);\n return found.length === 1 && found[0] === owner ? null : `the Service ${service} is announced by ${found.join(\", \") || \"no node\"}, not only by ${owner}`;\n });\n if (sole !== null) return sole;\n await cluster.kubectl([\"-n\", namespace, \"annotate\", \"service\", service, `${ANNOUNCE_ANNOTATION}=${Date.now()}`, \"--overwrite\"]);\n return null;\n};\n\n/**\n * This function installs MetalLB in a cluster, with its images on each node, the speakers on `nodes`,\n * and a pool of the VIP. The webhook of the controller checks the pool, thus the function tries the\n * pool again until the webhook answers. A failed install gives the pods of MetalLB and the tail of\n * their logs.\n *\n * With `memberlist`, the ConfigMap of the config file comes before the speakers. Then the function\n * checks that each speaker logged the load of the file (speakerlist.go, line 393). Without it, the\n * function checks that the ConfigMap does not exist.\n */\nexport const installMetallb = async (cluster: Cluster, images: MetallbImages, nodes: readonly string[], vip: string, memberlist?: MemberlistSettings): Promise<void> => {\n try {\n for (const node of nodes) await cluster.kubectl([\"label\", \"node\", node, `${VIP_LABEL}=1`, \"--overwrite\"]);\n if (memberlist !== undefined) {\n await cluster.apply([{ apiVersion: \"v1\", kind: \"Namespace\", metadata: { name: METALLB_NAMESPACE } }]);\n await cluster.apply([\n { apiVersion: \"v1\", kind: \"ConfigMap\", metadata: { name: MEMBERLIST_CONFIG, namespace: METALLB_NAMESPACE }, data: { \"config.json\": JSON.stringify(memberlistFileOf(memberlist)) } },\n ]);\n }\n await helmInstall(cluster, \"metallb\", METALLB_CHART, METALLB_NAMESPACE, metallbValues(images), { createNamespace: true });\n let failure: unknown = null;\n const end = Date.now() + 180_000;\n do {\n failure = await cluster.apply(metallbObjects(vip)).then(\n () => null,\n (error: unknown) => error,\n );\n if (failure !== null) await sleep(2000);\n } while (failure !== null && Date.now() < end);\n if (failure !== null) throw failure;\n if (memberlist === undefined) {\n const config = (await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"configmap\", MEMBERLIST_CONFIG, \"--ignore-not-found\", \"-o\", \"name\"])).trim();\n if (config !== \"\") throw new Error(`the ConfigMap ${MEMBERLIST_CONFIG} is there: memberlist would not use its defaults`);\n } else {\n const loaded = await until(120_000, async () => {\n const pods = JSON.parse(await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"pods\", \"-l\", \"app.kubernetes.io/component=speaker\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{ metadata: { name: string } }>;\n };\n if (pods.items.length !== nodes.length) return `${pods.items.length} speakers, not ${nodes.length}`;\n for (const pod of pods.items) {\n const log = await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"logs\", pod.metadata.name, \"-c\", \"speaker\"]).catch((error: unknown) => String(error));\n if (!log.includes(\"loading memberlist config from file\")) return `the speaker ${pod.metadata.name} did not log the load of the config file of memberlist`;\n }\n return null;\n });\n if (loaded !== null) throw new Error(loaded);\n }\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", METALLB_NAMESPACE, ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const logs = await state([\"logs\", \"-l\", \"app.kubernetes.io/name=metallb\", \"--tail=40\", \"--prefix\", \"--all-containers\"]);\n throw new Error(`the install of MetalLB failed: ${String(error)}\\nthe pods of MetalLB:\\n${pods}\\nthe logs:\\n${logs}`);\n }\n};\n\n/** These words mark the lines of a speaker that the record keeps: the events of memberlist, the announces and the withdraws. */\nconst KEPT = /node event|Marking|Suspect|Refuting|serviceAnnounced|serviceWithdrawn|memberlist/;\n\n/**\n * This function gives the record of MetalLB after a run: the nodes that announce the Service, and\n * some lines of each speaker. The lines are about memberlist and the announces, with the times of the kubelet.\n */\nexport const metallbRecord = async (cluster: Pick<Cluster, \"kubectl\">, namespace: string, service: string): Promise<Record<string, unknown>> => {\n const announcements = await announcementsOf(cluster, namespace, service);\n const text = await cluster\n .kubectl([\"-n\", METALLB_NAMESPACE, \"logs\", \"-l\", \"app.kubernetes.io/component=speaker\", \"-c\", \"speaker\", \"--since=300s\", \"--tail=4000\", \"--prefix\", \"--timestamps\"])\n .catch((error: unknown) => String(error));\n const log = text\n .split(\"\\n\")\n .filter((line) => KEPT.test(line))\n .slice(-600)\n .join(\"\\n\");\n // a run with a schedule of pauses (SYS-57) has several deaths and joins, thus the record keeps up to 600 lines\n return { announcements, log: log.slice(-90000) };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport type { KindCell, VipAnnouncerName, VipSettings } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { KUBE_VIP, KUBE_VIP_ARP_RATE, KUBE_VIP_IMAGE, KUBE_VIP_LEASE, holdVip, installKubeVip, vipLeaseOf, vipOf } from \"./kubevip.ts\";\nimport { METALLB_ANNOTATION, METALLB_IMAGES, METALLB_SPAM, holdMetallb, installMetallb, metallbRecord, metallbVipOf } from \"./metallb.ts\";\nimport { LB_CLASS, VIP_ANNOTATION } from \"./names.ts\";\n\n/**\n * This module gives one shape to each announcer of a VIP: kube-vip (SYS-51) and MetalLB in L2 mode\n * (SYS-53). The lab, the Service of the VIP and the clients outside the cluster are the same for\n * each one. Only the install, the choice of the VIP, the hold before each run and the record differ.\n */\n\n/** This is how the Service of a VIP selects its announcer: its class of load balancer, or none, and the annotation of its address. */\nexport interface VipService {\n readonly loadBalancerClass: string | null;\n readonly annotation: string;\n}\n\n/** An announcer is one way to give a Service of type LoadBalancer a VIP on the Docker network of the nodes. */\nexport interface VipAnnouncer {\n readonly name: VipAnnouncerName;\n /** These are the images of the announcer, pinned by digest, by their names in the manifest of a record. */\n readonly images: Readonly<Record<string, string>>;\n readonly service: VipService;\n /** This time is the most from the end of a hold to the first gratuitous ARP of the holder. */\n readonly settle: Quantity;\n /** This function gives the VIP of a subnet, with `owner` its holder among `nodes`. */\n readonly vipOf: (subnet: string, owner: string, nodes: readonly string[]) => string;\n /** This function installs the announcer, with the local tags of its images, by the names of `images`. MetalLB uses the settings of memberlist of `settings`. */\n readonly install: (cluster: Cluster, local: Readonly<Record<string, string>>, owner: string, nodes: readonly string[], vip: string, settings?: VipSettings) => Promise<void>;\n /** This function makes `owner` the holder of the VIP before a run. It gives null, or why not. */\n readonly hold: (cluster: Cluster, owner: string, nodes: readonly string[], namespace: string, service: string) => Promise<string | null>;\n /** This function gives the state of the announcer after a run, for the line `vip`. */\n readonly record: (cluster: Cluster, namespace: string, service: string) => Promise<Record<string, unknown>>;\n}\n\nconst local = (images: Readonly<Record<string, string>>, name: string): string => {\n const image = images[name];\n if (image === undefined) throw new Error(`the run has no local image of ${name}`);\n return image;\n};\n\n/** This announcer is kube-vip 1.3.1 in ARP mode, with one Lease for each VIP of a Service (`kubevip.ts`). */\nexport const KUBE_VIP_ANNOUNCER: VipAnnouncer = {\n name: \"kube-vip\",\n images: { [KUBE_VIP]: KUBE_VIP_IMAGE },\n service: { loadBalancerClass: LB_CLASS, annotation: VIP_ANNOTATION },\n settle: KUBE_VIP_ARP_RATE,\n vipOf: (subnet) => vipOf(subnet),\n install: (cluster, images, owner, nodes) => installKubeVip(cluster, local(images, KUBE_VIP), owner, nodes),\n hold: (cluster, owner, nodes) => holdVip(cluster, owner, nodes),\n record: async (cluster) => {\n const lease = await vipLeaseOf(cluster);\n const log = await cluster\n .kubectl([\"-n\", KUBE_VIP_LEASE.namespace, \"logs\", \"-l\", `app.kubernetes.io/name=${KUBE_VIP}`, \"--tail=30\", \"--prefix\", \"--timestamps\"])\n .catch((error: unknown) => String(error));\n return { lease, log: log.slice(-6000) };\n },\n};\n\n/** This announcer is MetalLB 0.16.1 in L2 mode, with its speakers in memberlist (`metallb.ts`). */\nexport const METALLB_ANNOUNCER: VipAnnouncer = {\n name: \"metallb\",\n images: { \"metallb-speaker\": METALLB_IMAGES.speaker, \"metallb-controller\": METALLB_IMAGES.controller },\n service: { loadBalancerClass: null, annotation: METALLB_ANNOTATION },\n settle: METALLB_SPAM.for,\n vipOf: (subnet, owner, nodes) => metallbVipOf(subnet, owner, nodes),\n install: (cluster, images, _owner, nodes, vip, settings) =>\n installMetallb(cluster, { speaker: local(images, \"metallb-speaker\"), controller: local(images, \"metallb-controller\") }, nodes, vip, settings?.memberlist),\n hold: (cluster, owner, nodes, namespace, service) => holdMetallb(cluster, owner, nodes, namespace, service),\n record: (cluster, namespace, service) => metallbRecord(cluster, namespace, service),\n};\n\n/** This function gives the announcer of the VIP of a cell: kube-vip when the cell names none. */\nexport const announcerOf = (cell: Pick<KindCell, \"vip\">): VipAnnouncer => (cell.vip?.announcer === \"metallb\" ? METALLB_ANNOUNCER : KUBE_VIP_ANNOUNCER);\n\n/** This function gives null if each client sends to `mac`, or the clients that do not. */\nexport const macsAt = (found: ReadonlyArray<{ readonly client: string; readonly mac: string | null }>, mac: string, count: number): string | null => {\n const wrong = found.filter((each) => each.mac !== mac);\n if (found.length !== count) return `${found.length} clients outside answer, not ${count}`;\n return wrong.length === 0 ? null : wrong.map((each) => `${each.client} has ${each.mac ?? \"no MAC\"}`).join(\", \");\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Backoff, KindCell } from \"./cell.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport type { PinnedChart } from \"./helm.ts\";\nimport { helmInstall } from \"./helm.ts\";\nimport { LOG_LABEL } from \"./names.ts\";\n\n/**\n * This module installs Apache Pulsar 5.0.0 in a kind cluster (SYS-56): one ZooKeeper, one bookie and\n * two brokers, from the Helm chart 4.8.0. It also gives the producers of the cell: a Python script\n * in the image of Pulsar, with its client 3.13.0 over the C++ client 4.2.0. Each producer sends to\n * one topic, and a thread of it looks up the owner of that topic at each probe.\n *\n * The fault is a SIGKILL of the JVM of the broker that owns the topic. Each term of the return of a\n * producer comes from a pinned source:\n * - The owner of a bundle is an ephemeral znode of the session of its broker. A lookup gives the\n * owner from the cache of the ownership, with no check of its life (pulsar-broker\n * NamespaceService.java at v5.0.0, lines 489–508). Thus each lookup names the stopped broker\n * until its session expires.\n * - The session timeout is `metadataStoreSessionTimeoutMillis`, 30000 (conf/broker.conf at v5.0.0,\n * line 187, and the chart, values.yaml line 1474). ZKMetadataStore.java gives it to ZooKeeper (line 105).\n * - The client of ZooKeeper 3.9.6 pings after 9 s with no send (ClientCnxn.java, lines 422, 1184\n * and 1263–1268). That is half of the read timeout of 2/3 of the session, less 1 s.\n * The server rounds each expiry up to the next tick of 2000 ms (ExpiryQueue.java, lines 53–54\n * and 87, and conf/zookeeper.conf of Pulsar, line 21). Thus the session expires 21–32 s after the stop.\n * - The broker that stays then takes the bundle at the next lookup.\n * - The chart starts a broker again only after its old znode is gone (templates/broker-statefulset.yaml,\n * lines 296–302). Thus the stopped broker cannot take the bundle back first.\n */\n\n/** This version is the release of Pulsar of the lab. */\nexport const PULSAR_VERSION = \"5.0.0\";\n\n/**\n * This chart is the Helm chart 4.8.0 of Pulsar. The sha256 is of the file of the release. The URL is\n * on the archive of Apache, which keeps each release. The mirror downloads.apache.org keeps only the\n * latest releases.\n */\nexport const PULSAR_CHART: PinnedChart = {\n name: \"pulsar\",\n version: \"4.8.0\",\n url: \"https://archive.apache.org/dist/pulsar/helm-chart/4.8.0/pulsar-4.8.0.tgz\",\n sha256: \"464f4a268463d187c9bc83b365b31de0b4a460533d948a04f846ba9b026068f5\",\n};\n\n/**\n * This image is Pulsar 5.0.0 for linux/amd64, pinned by the digest of the manifest of the platform.\n * It has ZooKeeper 3.9.6, BookKeeper, the broker, Python 3 and the Python client 3.13.0. The lab\n * pulls it by its digest, tags it (`localPin`), and loads it into each node.\n */\nexport const PULSAR_IMAGE = \"apachepulsar/pulsar:5.0.0@sha256:11e9a885121e16be91da77fdd3cfc3d4cbbece4700271d75e949585dbd3a5a5b\";\n\n/** This namespace holds Pulsar. The release of the chart is `pulsar` too. */\nexport const PULSAR_NAMESPACE = \"pulsar\";\n\n/** This name is the app of the producers of a cell. */\nexport const PRODUCER = \"producer\";\n\n/** This topic is the topic of the producers. */\nexport const PULSAR_TOPIC = \"persistent://public/default/lab\";\n\n/** This URL is the Service of the brokers for the binary protocol. */\nexport const PULSAR_SERVICE: string = `pulsar://pulsar-broker.${PULSAR_NAMESPACE}.svc.cluster.local:6650`;\n\n/** This URL is the lookup of the owner of the topic, on the Service of the brokers. */\nexport const PULSAR_LOOKUP: string = `http://pulsar-broker.${PULSAR_NAMESPACE}.svc.cluster.local:8080/lookup/v2/topic/persistent/public/default/lab`;\n\n/** This time is the session timeout of each broker in ZooKeeper (`metadataStoreSessionTimeoutMillis`). */\nexport const SESSION: Quantity = { value: 30, unit: \"s\" };\n\n/** This time is the tick of ZooKeeper (`tickTime`). The server rounds each expiry up to a tick. */\nexport const TICK: Quantity = { value: 2, unit: \"s\" };\n\n/** This time is the longest idle time of the client of ZooKeeper before its ping: the half of 2/3 of the session, less 1 s. */\nexport const PING: Quantity = { value: 9, unit: \"s\" };\n\n/**\n * This backoff is the backoff of a producer of the C++ client 4.2.0, with the defaults of the Python\n * client 3.13.0:\n * - The initial delay is 100 ms and the most is 60 s (lib/ClientConfigurationImpl.h, lines 39–40).\n * - The mandatory stop is the send timeout less 100 ms, and at least 100 ms (lib/ProducerImpl.cc,\n * lines 55–57). The send timeout of `create_producer` is 30000 ms (pulsar/__init__.py, line 1015).\n * - The jitter takes 0–9 % of each delay (lib/Backoff.cc, line 54).\n * - A try that fails takes the lookup and the connect, which a reset ends at once. The lab gives it 100 ms.\n */\nexport const PRODUCER_BACKOFF: Backoff = {\n initial: { value: 100, unit: \"ms\" },\n max: { value: 60, unit: \"s\" },\n mandatoryStop: { value: 29.9, unit: \"s\" },\n jitter: 0.09,\n attempt: { value: 100, unit: \"ms\" },\n};\n\n/** This function gives the repository and the tag of a local image, for the values of a chart. */\nconst repositoryOf = (local: string): { repository: string; tag: string } => {\n const at = local.lastIndexOf(\":\");\n if (at <= 0) throw new Error(`the image ${local} has no tag`);\n return { repository: local.slice(0, at), tag: local.slice(at + 1) };\n};\n\n/**\n * This function gives the values of the chart: ZooKeeper, one bookie and two brokers, with no\n * proxy, no autorecovery, no toolset and no monitor. The bookie keeps one copy of each entry, and\n * the brokers do not move a bundle by load. Each image is the local image, with no pull.\n */\nexport const pulsarValues = (image: string): Record<string, unknown> => {\n const { repository, tag } = repositoryOf(image);\n const noMonitor = { podMonitor: { enabled: false } };\n return {\n components: { zookeeper: true, bookkeeper: true, broker: true, autorecovery: false, proxy: false, toolset: false, functions: false, function_worker: false },\n \"victoria-metrics-k8s-stack\": { enabled: false },\n defaultPulsarImageRepository: repository,\n defaultPulsarImageTag: tag,\n defaultPullPolicy: \"Never\",\n affinity: { anti_affinity: false },\n volumes: { persistence: false },\n zookeeper: { replicaCount: 1, statefulsetUpgrade: { enabled: false }, ...noMonitor },\n bookkeeper: { replicaCount: 1, ...noMonitor },\n autorecovery: noMonitor,\n proxy: noMonitor,\n broker: {\n replicaCount: 2,\n statefulsetUpgrade: { enabled: false },\n ...noMonitor,\n configData: {\n managedLedgerDefaultEnsembleSize: \"1\",\n managedLedgerDefaultWriteQuorum: \"1\",\n managedLedgerDefaultAckQuorum: \"1\",\n loadBalancerSheddingEnabled: \"false\",\n loadBalancerAutoBundleSplitEnabled: \"false\",\n },\n },\n };\n};\n\n/**\n * This script is a producer of the cell. Each line that it writes is JSON, with the time of\n * CLOCK_MONOTONIC in milliseconds, the pod and the node:\n * - `send`: one send, at its start, with its time to the answer and its outcome. A send is `ok` within\n * the timeout, `late` after it, or the name of the result of the client (for example `timeout`).\n * - `lookup`: one lookup of the owner of the topic, with the host of the owner.\n * - `pulsar`: one line of the C++ client about the connection of the producer.\n */\nexport const PRODUCER_SCRIPT = `import json, logging, os, sys, threading, time, urllib.request\nimport pulsar\n\nPOD = os.environ[\"POD_NAME\"]\nNODE = os.environ[\"NODE_NAME\"]\nEVERY = int(os.environ[\"EVERY_MS\"]) / 1000\nTIMEOUT = int(os.environ[\"TIMEOUT_MS\"])\nPROBE = int(os.environ[\"PROBE_MS\"]) / 1000\nLOCK = threading.Lock()\nKEPT = (\"Schedule reconnection\", \"Getting connection from pool\", \"Created producer\", \"Connection closed\", \"Disconnected\", \"Lookup response\", \"Failed to\", \"Finished connecting\")\n\n\ndef now():\n return time.monotonic() * 1000\n\n\ndef say(**line):\n text = json.dumps({**line, \"p\": POD, \"n\": NODE}, separators=(\",\", \":\"))\n with LOCK:\n sys.stdout.write(text + \"\\\\n\")\n sys.stdout.flush()\n\n\nclass Relay(logging.Handler):\n def emit(self, record):\n message = record.getMessage()\n if any(word in message for word in KEPT):\n say(ev=\"pulsar\", t=now(), m=message[:400])\n\n\ndef probe():\n while True:\n start = now()\n owner = None\n outcome = \"ok\"\n try:\n with urllib.request.urlopen(os.environ[\"LOOKUP\"], timeout=1) as answer:\n data = json.loads(answer.read())\n url = data.get(\"brokerUrl\") or \"\"\n owner = url.split(\"://\", 1)[-1].rsplit(\":\", 1)[0] or None\n except Exception as error:\n outcome = type(error).__name__.lower()\n say(ev=\"lookup\", t=start, d=round(now() - start, 3), o=outcome, owner=owner)\n time.sleep(max(0.0, PROBE - (now() - start) / 1000))\n\n\ndef answered(start):\n def done(result, message_id):\n took = now() - start\n if result == pulsar.Result.Ok:\n outcome = \"ok\" if took <= TIMEOUT else \"late\"\n else:\n outcome = getattr(result, \"name\", str(result).split(\".\")[-1]).lower()\n say(ev=\"send\", t=start, d=round(took, 3), o=outcome)\n return done\n\n\nlogger = logging.getLogger(\"lab\")\nlogger.setLevel(logging.INFO)\nlogger.propagate = False\nlogger.addHandler(Relay())\nthreading.Thread(target=probe, daemon=True).start()\nclient = pulsar.Client(os.environ[\"SERVICE\"], logger=logger)\nwhile True:\n try:\n producer = client.create_producer(os.environ[\"TOPIC\"])\n break\n except Exception as error:\n say(ev=\"text\", t=now(), text=\"create_producer: \" + str(error)[:200])\n time.sleep(2)\nat = time.monotonic()\nwhile True:\n start = now()\n try:\n producer.send_async(b\"lab\", answered(start))\n except Exception as error:\n say(ev=\"send\", t=start, d=0, o=\"refused-\" + type(error).__name__.lower())\n at += EVERY\n time.sleep(max(0.0, at - time.monotonic()))\n`;\n\n/**\n * This function gives the producers of a cell in its namespace. They are the script in a ConfigMap,\n * and a Deployment of `clients` pods in the local image of Pulsar, one on each worker.\n */\nexport const pulsarObjects = (cell: KindCell, namespace: string, image: string): unknown[] => {\n if (cell.pulsar === undefined) throw new Error(`${cell.name} has no broker`);\n const env = {\n SERVICE: PULSAR_SERVICE,\n TOPIC: PULSAR_TOPIC,\n LOOKUP: PULSAR_LOOKUP,\n EVERY_MS: String(valueIn(cell.workload.every, \"ms\")),\n TIMEOUT_MS: String(valueIn(cell.workload.timeout, \"ms\")),\n PROBE_MS: String(valueIn(cell.pulsar.probe, \"ms\")),\n PYTHONUNBUFFERED: \"1\",\n };\n return [\n { apiVersion: \"v1\", kind: \"ConfigMap\", metadata: { name: PRODUCER, namespace }, data: { \"producer.py\": PRODUCER_SCRIPT } },\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: PRODUCER, namespace, labels: { app: PRODUCER } },\n spec: {\n replicas: cell.pulsar.clients,\n selector: { matchLabels: { app: PRODUCER } },\n template: {\n metadata: { labels: { app: PRODUCER, [LOG_LABEL]: \"1\" } },\n spec: {\n terminationGracePeriodSeconds: 0,\n topologySpreadConstraints: [{ maxSkew: 1, topologyKey: \"kubernetes.io/hostname\", whenUnsatisfiable: \"DoNotSchedule\", labelSelector: { matchLabels: { app: PRODUCER } } }],\n volumes: [{ name: \"script\", configMap: { name: PRODUCER } }],\n containers: [\n {\n name: PRODUCER,\n image,\n imagePullPolicy: \"Never\",\n command: [\"python3\", \"/lab/producer.py\"],\n volumeMounts: [{ name: \"script\", mountPath: \"/lab\" }],\n env: [\n ...Object.entries(env).map(([name, value]) => ({ name, value })),\n { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } },\n { name: \"NODE_NAME\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/** A broker is a pod of a broker: its name, its node, its address, and if it is ready. */\nexport interface Broker {\n readonly pod: string;\n readonly node: string;\n readonly ip: string;\n readonly ready: boolean;\n}\n\n/** This function gives the pods of the brokers, in the sequence of their names. */\nexport const brokersOf = async (cluster: Pick<Cluster, \"kubectl\">): Promise<Broker[]> => {\n const list = JSON.parse(await cluster.kubectl([\"-n\", PULSAR_NAMESPACE, \"get\", \"pods\", \"-l\", \"component=broker\", \"-o\", \"json\"])) as {\n items: ReadonlyArray<{\n metadata: { name: string; deletionTimestamp?: string };\n spec: { nodeName?: string };\n status?: { podIP?: string; containerStatuses?: ReadonlyArray<{ ready: boolean }> };\n }>;\n };\n return list.items\n .filter((pod) => pod.metadata.deletionTimestamp === undefined)\n .map((pod) => ({\n pod: pod.metadata.name,\n node: pod.spec.nodeName ?? \"\",\n ip: pod.status?.podIP ?? \"\",\n ready: (pod.status?.containerStatuses ?? []).length > 0 && pod.status!.containerStatuses!.every((status) => status.ready),\n }))\n .sort((a, b) => a.pod.localeCompare(b.pod));\n};\n\n/** This function gives the broker that a host of a lookup names. Its pod is the first label of the host, or its address is the host. */\nexport const brokerOfHost = (brokers: readonly Broker[], host: string): Broker | undefined =>\n brokers.find((broker) => host.split(\".\")[0] === broker.pod || host === broker.ip);\n\n/** This is the state of the producers that the hold reads. It has the last lookup and the last sends of each producer, and the last time of a line. */\nexport interface ProducerState {\n readonly owners: ReadonlyMap<string, { readonly t: number; readonly owner: string | null }>;\n readonly sends: ReadonlyMap<string, { readonly ok: number; readonly failed: number }>;\n readonly last: number;\n}\n\n/**\n * This function tells why the producers are not at rest, or gives the host of the owner of the\n * topic. At rest, each of `clients` producers had an answer in the last second and no failure in\n * the last 2 s. The last lookup of each one is in the last 2 s, and each names the same owner.\n */\nexport const ownerAtRest = (state: ProducerState, clients: number): { readonly owner: string } | string => {\n if (state.sends.size !== clients) return `${state.sends.size} producers send, not ${clients}`;\n for (const [pod, send] of state.sends) {\n if (state.last - send.ok > 1000) return `the producer ${pod} has no answer in the last second`;\n if (state.last - send.failed < 2000) return `the producer ${pod} failed in the last 2 s`;\n }\n const owners = new Set<string>();\n for (const pod of state.sends.keys()) {\n const seen = state.owners.get(pod);\n if (seen === undefined || seen.owner === null || state.last - seen.t > 2000) return `the producer ${pod} has no lookup with an owner in the last 2 s`;\n owners.add(seen.owner);\n }\n if (owners.size !== 1) return `the lookups name ${[...owners].join(\" and \")}`;\n return { owner: [...owners][0]! };\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * This function waits until each broker is ready and the producers are at rest (`ownerAtRest`). It\n * gives the broker that owns the topic and the host that the lookups name, or why it could not in `within` ms.\n */\nexport const holdPulsar = async (\n cluster: Pick<Cluster, \"kubectl\">,\n state: () => ProducerState,\n clients: number,\n within = 300_000,\n): Promise<{ readonly broker: Broker; readonly host: string } | string> => {\n const end = Date.now() + within;\n let why = \"the hold did not start\";\n do {\n const brokers = await brokersOf(cluster);\n const busy = brokers.find((broker) => !broker.ready);\n const rest = ownerAtRest(state(), clients);\n if (brokers.length !== 2) why = `${brokers.length} brokers, not 2`;\n else if (busy !== undefined) why = `the broker ${busy.pod} is not ready`;\n else if (typeof rest === \"string\") why = rest;\n else {\n const broker = brokerOfHost(brokers, rest.owner);\n if (broker !== undefined) return { broker, host: rest.owner };\n why = `the owner ${rest.owner} is no broker`;\n }\n await sleep(500);\n } while (Date.now() < end);\n return why;\n};\n\n/**\n * This function installs Pulsar in a cluster, with its image on each node. A failed install gives\n * the pods of Pulsar and the tail of their logs.\n */\nexport const installPulsar = async (cluster: Cluster, image: string): Promise<void> => {\n try {\n await helmInstall(cluster, \"pulsar\", PULSAR_CHART, PULSAR_NAMESPACE, pulsarValues(image), { createNamespace: true });\n } catch (error) {\n const state = async (args: readonly string[]): Promise<string> =>\n (await cluster.kubectl([\"-n\", PULSAR_NAMESPACE, ...args]).catch((why: unknown) => String(why))).slice(-4000);\n const pods = await state([\"get\", \"pods\", \"-o\", \"wide\"]);\n const logs = await state([\"logs\", \"-l\", \"app=pulsar\", \"--tail=30\", \"--prefix\", \"--all-containers\"]);\n throw new Error(`the install of Pulsar failed: ${String(error)}\\nthe pods of Pulsar:\\n${pods}\\nthe logs:\\n${logs}`);\n }\n};\n\n/** These words mark the lines of a broker that the record keeps: the sessions, the leader, the ownership and the topics. */\nconst KEPT = /[Ss]ession|[Ee]xpired|[Ll]eader|[Oo]wnership|[Bb]undle|Loaded|[Ll]oading topic|Created new producer|Disconnect|Closed connection|znode|ManagedLedger.*(open|recover)/;\n\n/**\n * This function gives the record of Pulsar after a run. It has the brokers, and the kept lines of\n * each broker and of its last container, with the times of the kubelet.\n */\nexport const pulsarRecord = async (cluster: Pick<Cluster, \"kubectl\">): Promise<Record<string, unknown>> => {\n const brokers = await brokersOf(cluster);\n const logs: Record<string, string> = {};\n for (const broker of brokers) {\n for (const previous of [false, true]) {\n const text = await cluster\n .kubectl([\"-n\", PULSAR_NAMESPACE, \"logs\", broker.pod, \"-c\", \"pulsar-broker\", \"--since=300s\", \"--tail=6000\", \"--timestamps\", ...(previous ? [\"--previous\"] : [])])\n .catch((error: unknown) => String(error));\n logs[`${broker.pod}${previous ? \", the last container\" : \"\"}`] = text\n .split(\"\\n\")\n .filter((line) => KEPT.test(line))\n .slice(-80)\n .join(\"\\n\")\n .slice(-8000);\n }\n }\n return { brokers, logs };\n};\n","// This module has no imports: the agent of the lab image imports it as it is.\n\n/**\n * This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS\n * moves (`/stats?filter=`, `source/server/admin/stats_params.cc` of Envoy v1.39.4). An update of a\n * listener counts `listener_modified`. An update in place, of its filter chains only, also counts\n * `listener_in_place_updated` (`listener_manager_impl.cc:644`, `:692`).\n */\nexport const LDS_FILTER = \"^listener_manager\\\\.listener_(in_place_updated|modified)$\";\n\n/** The listener counts are the two counters of LDS of one sidecar: each update (`mod`), and each update in place (`inPlace`). */\nexport interface ListenerCounts {\n readonly mod: number;\n readonly inPlace: number;\n}\n\n/**\n * This function gives the listener counts of the text of `/stats`, one `name: value` on each line\n * (`stats_render.cc:15`). It gives null when the text has no counter `listener_modified`.\n */\nexport const listenerCountsOf = (text: string): ListenerCounts | null => {\n let mod: number | null = null;\n let inPlace = 0;\n for (const line of text.split(\"\\n\")) {\n const match = /^listener_manager\\.listener_(in_place_updated|modified): (\\d+)$/.exec(line.trim());\n if (match === null) continue;\n if (match[1] === \"modified\") mod = Number(match[2]);\n else inPlace = Number(match[2]);\n }\n return mod === null ? null : { mod, inPlace };\n};\n","// The parser of the conntrack table of a node. The agent of each node reads the table with it, and\n// the image copies this file, thus it imports nothing.\n\n/** A flow is one conntrack entry of TCP: its state, its life in seconds, its original tuple and the source of its replies. */\nexport interface ConntrackFlow {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow, as `ip:port`. */\n readonly from: string;\n /** This address is the original destination of the flow, as `ip:port`: a ClusterIP before its DNAT. */\n readonly to: string;\n /** This address is the source of the replies, as `ip:port`: the backend that the DNAT of the entry chose. */\n readonly reply: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/** An entry is one conntrack entry of a TCP flow to the ClusterIP. It has a state, a life in seconds, a client and the backend of its NAT. */\nexport interface ConntrackEntry {\n readonly state: string;\n readonly ttl: number;\n /** This address is the original source of the flow: the client, as `ip:port`. */\n readonly client: string;\n /** This address is the source of the replies: the backend that the DNAT of the entry chose, as `ip:port`. */\n readonly backend: string;\n /** This flag tells that no packet came back on the flow. */\n readonly unreplied: boolean;\n}\n\n/**\n * These states keep the NAT of an entry for a new SYN on the same port (nf_conntrack_proto_tcp.c at\n * v6.8). A SYN on an entry in TIME_WAIT removes the entry and looks up again (:1011-1027), thus that\n * SYN gets a backend from the rules. In each other state, the SYN goes to the backend of the entry.\n * A CLOSE that a reset from the server made does not reopen either.\n */\nexport const KEEPS_NAT: ReadonlySet<string> = new Set([\"SYN_SENT\", \"SYN_SENT2\", \"SYN_RECV\", \"ESTABLISHED\", \"FIN_WAIT\", \"CLOSE_WAIT\", \"LAST_ACK\", \"CLOSE\"]);\n\n/**\n * These are the lives of an entry in the states of a handshake, in seconds\n * (nf_conntrack_tcp_timeout_syn_sent and _syn_recv). A retransmission of the SYN does not renew the\n * life of SYN_SENT (nf_conntrack_proto_tcp.c at v6.8). Thus the life of such an entry tells the age\n * of its first SYN.\n */\nexport const HANDSHAKE_LIFE: Readonly<Record<string, number>> = { SYN_SENT: 120, SYN_RECV: 60 };\n\n/** This form is one TCP line of `/proc/net/nf_conntrack` or `conntrack -L`. It has the timeout and state, the original tuple, the flags and counters, then the reply tuple. */\nconst LINE = /\\btcp\\s+\\d+\\s+(\\d+)\\s+(\\S+)\\s+src=(\\S+) dst=(\\S+) sport=(\\d+) dport=(\\d+)(.*?)\\bsrc=(\\S+) dst=\\S+ sport=(\\d+) dport=\\d+/;\n\n/** This function gives the TCP flows of a dump of the conntrack table. */\nexport const flowsOf = (text: string): ConntrackFlow[] => {\n const out: ConntrackFlow[] = [];\n for (const line of text.split(\"\\n\")) {\n const match = LINE.exec(line);\n if (match === null) continue;\n out.push({\n ttl: Number(match[1]),\n state: match[2]!,\n from: `${match[3]}:${match[5]}`,\n to: `${match[4]}:${match[6]}`,\n reply: `${match[8]}:${match[9]}`,\n unreplied: match[7]!.includes(\"[UNREPLIED]\"),\n });\n }\n return out;\n};\n\n/** This function gives the TCP entries of a dump of the conntrack table whose original destination is the ClusterIP and port. */\nexport const entriesOf = (text: string, ip: string, port: number): ConntrackEntry[] =>\n flowsOf(text)\n .filter((flow) => flow.to === `${ip}:${port}`)\n .map((flow) => ({ state: flow.state, ttl: flow.ttl, client: flow.from, backend: flow.reply, unreplied: flow.unreplied }));\n\n/**\n * This function gives the entries that keep their NAT to a pod that is not a backend of the rules.\n * A new SYN from the client port of such an entry goes to that pod, whatever the rules say.\n */\nexport const staleOf = (entries: readonly ConntrackEntry[], backends: readonly string[]): ConntrackEntry[] =>\n entries.filter((entry) => KEEPS_NAT.has(entry.state) && !backends.includes(entry.backend));\n\n/** This function gives the count of entries that `conntrack -D` deleted, from its last line: \"N flow entries have been deleted\". */\nexport const deletedOf = (text: string): number => {\n const match = /(\\d+) flow entr(?:y|ies) ha(?:s|ve) been deleted/.exec(text);\n if (match === null) throw new Error(`conntrack -D gave no count: ${text.trim().slice(0, 200)}`);\n return Number(match[1]);\n};\n\n/**\n * This function gives the flows whose handshake stopped `age` seconds ago or more. Such a flow is\n * a SYN with no reply (SYN_SENT), or a SYN and its SYN-ACK with no ACK (SYN_RECV). It is a\n * connection that times out. On the node of the client, its reply address is the backend of its\n * DNAT. On the node of that backend, a SYN_SENT tells that the backend did not answer. There, a\n * SYN_RECV tells that its answer did not come back.\n */\nexport const stalledOf = (flows: readonly ConntrackFlow[], age: number): ConntrackFlow[] =>\n flows.filter((flow) => {\n const life = HANDSHAKE_LIFE[flow.state];\n if (life === undefined || (flow.state === \"SYN_SENT\" && !flow.unreplied)) return false;\n return flow.ttl <= life - age;\n });\n","import type { StartedTestContainer } from \"testcontainers\";\nimport { harnessImage } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { ServiceRule } from \"./backends.ts\";\nimport { deletedOf } from \"./conntrack.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command } from \"./cluster.ts\";\nimport { OUTSIDE } from \"./names.ts\";\nimport type { Line } from \"./observe.ts\";\nimport { isTunnelState } from \"./observe.ts\";\n\nexport type { Line } from \"./observe.ts\";\n\n/** A stamp brackets an act on CLOCK_MONOTONIC, in milliseconds. */\nexport interface Stamp {\n readonly t0: number;\n readonly t1: number;\n}\n\n/**\n * These kinds of kernel record stay in the raw record of a run. The lab also keeps each change of the\n * state of a socket of the HBONE port (`isTunnelState`): the tunnels of ztunnel. It counts the other\n * records and drops them.\n */\nexport const KEPT_KERNEL: ReadonlySet<string> = new Set([\"rst_tx\", \"rst_rx\", \"rst_v4\", \"error\", \"retrans\", \"begin\", \"fail\"]);\n\n/** This function tells if the lab keeps a kernel record of the prober. */\nexport const keptKernel = (record: Readonly<Record<string, unknown>>): boolean => KEPT_KERNEL.has(String(record[\"ev\"])) || isTunnelState(record);\n\n/** 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. */\nexport interface EdsSpec {\n readonly cluster: string;\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/** An LDS spec is the interval of the LDS pollers, and each server pod with its node and a PID in it. */\nexport interface LdsSpec {\n readonly every: Quantity;\n readonly pods: ReadonlyArray<{ readonly node: string; readonly pod: string; readonly pid: number }>;\n}\n\n/**\n * This function gives the MAC address of `ip` in a table of /proc/net/arp, in lower case. It gives\n * null if the table has no row for `ip`, or if the row has the flags 0x0: no MAC yet. The ARP watch\n * of `client.mjs` reads the table the same way.\n */\nexport const arpMacOf = (table: string, ip: string): string | null => {\n for (const row of table.split(\"\\n\").slice(1)) {\n const [address, , flags, mac] = row.trim().split(/\\s+/);\n if (address === ip) return flags === \"0x0\" || mac === undefined ? null : mac.toLowerCase();\n }\n return null;\n};\n\n/** An outside spec is the clients outside the cluster: their target, interval, timeout and count, and the address whose MAC they watch. */\nexport interface OutsideSpec {\n readonly target: string;\n readonly every: Quantity;\n readonly timeout: Quantity;\n readonly count: number;\n readonly arp: string;\n}\n\n/** These are the sidecars of one node: the agent of the lab and the injector of the harness, in the PID namespace of the node. */\ninterface Sidecars {\n readonly agent: StartedTestContainer;\n readonly injector: StartedTestContainer;\n}\n\n/** A kind lab is the instruments of one cluster: a sidecar pair on each node, the watcher of the API and the prober. */\nexport interface KindLab {\n readonly cluster: Cluster;\n readonly image: string;\n /** These are the lines since the last `take()`. */\n readonly lines: Line[];\n /** This record counts the kernel records that the lab dropped, by kind. */\n readonly dropped: Record<string, number>;\n /** This method gives the names of the containers that the lab started: they are not the lab containers of another agent. */\n own(): string[];\n /** This method gives the lines since the last call, and starts a new buffer. */\n take(): Line[];\n /** This method adds a listener of each line, and gives the function that removes it. */\n listen(listener: (line: Line) => void): () => void;\n /** This method adds a line of the runner to the lines: for example, evidence that the runner reads with a tool. */\n record(line: Line): void;\n /**\n * This method starts the watcher of the API on the namespaces. With `ztunnel`, it also follows the\n * log of each ztunnel in istio-system: lines `ztunnel-pod` and `ztunnel`, which no tracker of the\n * cell reads.\n */\n watch(namespaces: readonly string[], options?: { readonly ztunnel?: boolean | undefined }): Promise<void>;\n probe(netns: readonly number[]): Promise<void>;\n stopProbe(): Promise<void>;\n /**\n * This method starts the clients outside the cluster of a VIP (`KindCell.vip`). Each one is a\n * container of the lab image on the Docker network of the nodes, and runs `client.mjs`. Its lines\n * have the node `outside`, and a line `arp` at each change of the MAC address of the VIP. A\n * call stops the clients of the last call first. With null, it only stops them.\n */\n outside(spec: OutsideSpec | null): Promise<void>;\n /** This method gives the MAC address of `ip` in the neighbour table of each client outside the cluster. It is null for a client with no MAC for it. */\n outsideMacs(ip: string): Promise<ReadonlyArray<{ readonly client: string; readonly mac: string | null }>>;\n /** This method asks each agent to poll the backends of a Service in the rules of its node, `every` apart, or to stop with null. */\n rules(rule: (ServiceRule & { readonly every: Quantity }) | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each client pod of its node, `every` apart.\n * Each poll gives the hosts of one cluster. With null, the polls stop. A pod gives the PID of one\n * of its processes, and the agent enters the network namespace of that PID.\n */\n eds(spec: EdsSpec | null): Promise<void>;\n /**\n * This method asks each agent to poll the sidecar of each server pod of its node, `every` apart.\n * Each poll gives the counters of LDS updates of that sidecar (`lds.ts`). With null, the polls stop.\n */\n lds(spec: LdsSpec | null): Promise<void>;\n act(node: string, spec: Readonly<Record<string, unknown>>): Promise<Stamp>;\n inject(node: string, pid: number, args: readonly string[]): Promise<Stamp>;\n /**\n * This method gives the rules of one table of iptables in the network namespace of a PID, with\n * their counters (`iptables-save -c`). The injector reads them. The method also adds them to the\n * lines as a line `tables`: evidence of which rules matched.\n */\n tables(node: string, pid: number, table: string): Promise<string>;\n /**\n * This method runs commands of the injector of the harness in the network namespace of a PID. Each\n * command is a sysctl or a rule of iptables, in sequence. It stops at the first command that fails. Each word of\n * a command is plain: letters, digits and `_ . , : = / + -`. The method adds the commands and their\n * output to the lines as a line `act`, and gives the output.\n */\n netns(node: string, pid: number, commands: ReadonlyArray<readonly string[]>): Promise<string>;\n now(): Promise<number>;\n pidOf(node: string, namespace: string, pod: string, container: string): Promise<number>;\n netnsOf(node: string, pid: number): Promise<number>;\n /** This method gives the path of the cgroup of a process, as the agent reaches it through the root of the node. */\n cgroupOf(node: string, pid: number): Promise<string>;\n /**\n * This method deletes the conntrack entries in SYN_SENT to a ClusterIP and port on each node, and\n * gives the count of each node. Such an entry is a SYN with no answer. It keeps its NAT for 120 s,\n * and a new SYN from the same port goes where it went.\n */\n forgetHandshakes(ip: string, port: number): Promise<Record<string, number>>;\n close(): Promise<void>;\n}\n\n/** This container class can share the process namespace of another container. */\nconst shared = async () => {\n const { GenericContainer } = await import(\"testcontainers\");\n return class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n};\n\n/** This function collects the JSON lines of a stream, and gives each one to `take`. */\nconst consume = (take: (line: Record<string, unknown>) => void) => (stream: NodeJS.ReadableStream) => {\n let partial = \"\";\n stream.on(\"data\", (chunk: Buffer | string) => {\n partial += chunk.toString();\n const split = partial.split(\"\\n\");\n partial = split.pop() ?? \"\";\n for (const text of split) {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"{\")) continue;\n try {\n take(JSON.parse(trimmed) as Record<string, unknown>);\n } catch {\n // a line that is not whole JSON is not a record\n }\n }\n });\n};\n\n/** This function gives a stamp of the injector of the harness, in nanoseconds, as milliseconds. */\nconst injectorStamp = (stdout: string): Stamp => {\n const stamp = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { t0: number | string; t1: number | string };\n return { t0: Number(BigInt(stamp.t0)) / 1e6, t1: Number(BigInt(stamp.t1)) / 1e6 };\n};\n\nconst output = async (container: StartedTestContainer, argv: readonly string[]): Promise<string> => {\n const result = await container.exec([...argv]);\n if (result.exitCode !== 0) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 500)}`);\n return result.stdout;\n};\n\n/**\n * This function opens the instruments of a cluster. Each node gets the agent of the lab and the\n * injector of the harness, privileged and in the PID namespace of the node. Thus the agent signals\n * a process of a pod by its PID on the node. The injector enters the network namespace of a pod\n * with `nsenter`.\n */\nexport const openKindLab = async (cluster: Cluster, image: string): Promise<KindLab> => {\n const harness = await harnessImage();\n const Shared = await shared();\n const { GenericContainer } = await import(\"testcontainers\");\n let lines: Line[] = [];\n const dropped: Record<string, number> = {};\n const listeners = new Set<(line: Line) => void>();\n const push = (line: Line): void => {\n lines.push(line);\n for (const listener of listeners) listener(line);\n };\n const sidecars = new Map<string, Sidecars>();\n const opened: StartedTestContainer[] = [];\n let watcher: StartedTestContainer | null = null;\n let prober: StartedTestContainer | null = null;\n let outsiders: StartedTestContainer[] = [];\n const stopOutside = async (): Promise<void> => {\n for (const container of outsiders) await container.stop({ timeout: 0 }).catch(() => undefined);\n outsiders = [];\n };\n try {\n for (const node of cluster.nodes) {\n const agent = await new Shared(image)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withEnvironment({ NODE_NAME: node })\n .withCommand([\"node\", \"agent.mjs\", \"serve\"])\n .withLogConsumer(consume((line) => push({ ...(line as unknown as Line), node })))\n .start();\n opened.push(agent);\n const injector = await new Shared(harness)\n .withPidMode(`container:${node}`)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .start();\n opened.push(injector);\n sidecars.set(node, { agent, injector });\n }\n } catch (error) {\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n throw error;\n }\n const sidecarOf = (node: string): Sidecars => {\n const found = sidecars.get(node);\n if (found === undefined) throw new Error(`the cluster has no node ${node}`);\n return found;\n };\n const firstNode = cluster.nodes[0]!;\n /**\n * This function writes the file of the pollers of one kind on each agent: `fields`, the interval\n * and the pods of its node. A node without a pod, or a spec of null, gets no file.\n */\n const pollersOf = async (\n kind: \"eds\" | \"lds\",\n fields: Readonly<Record<string, unknown>> | null,\n spec: { readonly every: Quantity; readonly pods: LdsSpec[\"pods\"] } | null,\n ): Promise<void> => {\n for (const [node, { agent }] of sidecars) {\n const pods = spec === null ? [] : spec.pods.filter((pod) => pod.node === node).map(({ pod, pid }) => ({ pod, pid }));\n // the agent reads the interval of its poll in milliseconds\n const file = spec === null || pods.length === 0 ? null : JSON.stringify({ ...fields, every: valueIn(spec.every, \"ms\"), pods });\n if (file === null) await output(agent, [\"rm\", \"-f\", `/tmp/${kind}.json`]);\n // a rename is atomic, thus the agent never reads a part of the file\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/${kind}.json.partial && mv /tmp/${kind}.json.partial /tmp/${kind}.json`]);\n }\n };\n const lab: KindLab = {\n cluster,\n image,\n get lines() {\n return lines;\n },\n dropped,\n own() {\n return [...opened, ...(watcher === null ? [] : [watcher]), ...(prober === null ? [] : [prober]), ...outsiders].map((container) => container.getName().replace(/^\\//, \"\"));\n },\n take() {\n const out = lines;\n lines = [];\n return out;\n },\n record(line) {\n push(line);\n },\n listen(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async watch(namespaces, options) {\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n const kube = await cluster.credentials();\n watcher = await new GenericContainer(image)\n .withNetworkMode(cluster.network)\n .withEnvironment({ KUBE: JSON.stringify(kube), NAMESPACES: namespaces.join(\",\"), ZTUNNEL: options?.ztunnel === true ? \"1\" : \"0\" })\n .withCommand([\"node\", \"watch.mjs\"])\n .withLogConsumer(consume((line) => push(line as unknown as Line)))\n .start();\n },\n async probe(netns) {\n await lab.stopProbe();\n let begun: (ok: boolean, why: string) => void = () => {};\n const ready = new Promise<void>((resolve, reject) => {\n begun = (ok, why) => (ok ? resolve() : reject(new Error(`the prober failed: ${why}`)));\n });\n prober = await new GenericContainer(harness)\n .withPrivilegedMode()\n .withNetworkMode(\"none\")\n .withCommand([\"/opt/harness/prober\", ...netns.map(String)])\n .withLogConsumer(\n consume((record) => {\n const ev = String(record[\"ev\"]);\n if (ev === \"begin\") begun(true, \"\");\n if (ev === \"fail\") begun(false, String(record[\"why\"]));\n if (!keptKernel(record) && ev !== \"clock\") {\n dropped[ev] = (dropped[ev] ?? 0) + 1;\n return;\n }\n const mono = Number(record[\"mono\"] ?? 0);\n push({ ...(record as unknown as Line), ev: `kernel.${ev}`, t: mono / 1e6 });\n }),\n )\n .start();\n await Promise.race([\n ready,\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"no begin line from the prober in 60 s\")), 60_000)),\n ]);\n },\n async outside(spec) {\n await stopOutside();\n if (spec === null) return;\n for (let k = 0; k < spec.count; k += 1) {\n const client = await new GenericContainer(image)\n .withNetworkMode(cluster.network)\n .withEnvironment({\n TARGET: spec.target,\n EVERY_MS: String(valueIn(spec.every, \"ms\")),\n TIMEOUT_MS: String(valueIn(spec.timeout, \"ms\")),\n POD_NAME: `${OUTSIDE}-${k}`,\n NODE_NAME: OUTSIDE,\n ARP_WATCH: spec.arp,\n })\n .withCommand([\"node\", \"client.mjs\"])\n .withLogConsumer(consume((line) => push(line as unknown as Line)))\n .start();\n outsiders.push(client);\n }\n },\n async outsideMacs(ip) {\n const found: Array<{ readonly client: string; readonly mac: string | null }> = [];\n for (const [k, client] of outsiders.entries()) {\n const { output } = await client.exec([\"cat\", \"/proc/net/arp\"]);\n found.push({ client: `${OUTSIDE}-${k}`, mac: arpMacOf(output, ip) });\n }\n return found;\n },\n async stopProbe() {\n if (prober !== null) await prober.stop({ timeout: 0 }).catch(() => undefined);\n prober = null;\n },\n async rules(rule) {\n // the agent reads the interval of its poll in milliseconds\n const file = rule === null ? null : JSON.stringify({ ...rule, every: valueIn(rule.every, \"ms\") });\n for (const { agent } of sidecars.values()) {\n if (file === null) await output(agent, [\"rm\", \"-f\", \"/tmp/rules.json\"]);\n else await output(agent, [\"sh\", \"-c\", `printf '%s' '${file}' > /tmp/rules.json`]);\n }\n },\n async eds(spec) {\n await pollersOf(\"eds\", spec === null ? null : { cluster: spec.cluster }, spec);\n },\n async lds(spec) {\n await pollersOf(\"lds\", {}, spec);\n },\n async forgetHandshakes(ip, port) {\n const out: Record<string, number> = {};\n for (const [node, { agent }] of sidecars) {\n const argv = [\"nsenter\", \"-t\", \"1\", \"-m\", \"-n\", \"--\", \"conntrack\", \"-D\", \"-p\", \"tcp\", \"--orig-dst\", ip, \"--dport\", String(port), \"--state\", \"SYN_SENT\"];\n // the tool writes its count to stderr, and exits 1 when it deletes no entry\n const result = await agent.exec(argv);\n if (result.exitCode !== 0 && result.exitCode !== 1) throw new Error(`${argv.join(\" \")} exited ${result.exitCode}: ${result.output.trim().slice(0, 300)}`);\n out[node] = deletedOf(result.output);\n }\n return out;\n },\n async act(node, spec) {\n const stdout = await output(sidecarOf(node).agent, [\"node\", \"agent.mjs\", \"act\", JSON.stringify(spec)]);\n const result = JSON.parse(stdout.trim().split(\"\\n\").at(-1)!) as { ok: boolean; t0: number; t1: number; why?: string };\n if (!result.ok) throw new Error(`the act ${JSON.stringify(spec)} on ${node} failed: ${result.why}`);\n push({ ev: \"act\", t: result.t0, t1: result.t1, node, spec });\n return { t0: result.t0, t1: result.t1 };\n },\n async inject(node, pid, args) {\n const stdout = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"/opt/harness/inject\", ...args]);\n const stamp = injectorStamp(stdout);\n push({ ev: \"act\", t: stamp.t0, t1: stamp.t1, node, spec: { op: \"inject\", pid, args } });\n return stamp;\n },\n async tables(node, pid, table) {\n const t = await lab.now();\n const text = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"iptables-save\", \"-c\", \"-t\", table]);\n push({ ev: \"tables\", t, node, pid, table, text: text.slice(0, 8000) });\n return text;\n },\n async netns(node, pid, commands) {\n // the words go to a shell as they are, thus each one is plain\n for (const word of commands.flat()) if (!/^[A-Za-z0-9_.,:=/+-]+$/.test(word)) throw new Error(`not a plain word of a command: ${word}`);\n if (commands.length === 0) throw new Error(\"no command\");\n const t = await lab.now();\n const text = await output(sidecarOf(node).injector, [\"nsenter\", \"-t\", String(pid), \"-n\", \"sh\", \"-c\", commands.map((argv) => argv.join(\" \")).join(\" && \")]);\n push({ ev: \"act\", t, t1: await lab.now(), node, spec: { op: \"netns\", pid, commands }, out: text.slice(0, 2000) });\n return text;\n },\n async now() {\n return (await lab.act(firstNode, { op: \"now\" })).t0;\n },\n async pidOf(node, namespace, pod, container) {\n const ids = (\n await cluster.exec(node, [\n \"crictl\",\n \"ps\",\n \"-q\",\n \"--state\",\n \"running\",\n \"--label\",\n `io.kubernetes.pod.namespace=${namespace}`,\n \"--label\",\n `io.kubernetes.pod.name=${pod}`,\n \"--name\",\n `^${container}$`,\n ])\n )\n .split(/\\s+/)\n .filter((id) => id !== \"\");\n if (ids.length !== 1) throw new Error(`${namespace}/${pod}/${container} on ${node} has ${ids.length} running containers, not 1`);\n const info = JSON.parse(await cluster.exec(node, [\"crictl\", \"inspect\", ids[0]!])) as { info?: { pid?: number } };\n const pid = info.info?.pid;\n if (pid === undefined || !(pid > 1)) throw new Error(`crictl gives no PID for ${namespace}/${pod}/${container}`);\n return pid;\n },\n async netnsOf(node, pid) {\n const link = (await cluster.exec(node, [\"readlink\", `/proc/${pid}/ns/net`])).trim();\n const inode = /^net:\\[(\\d+)\\]$/.exec(link)?.[1];\n if (inode === undefined) throw new Error(`not a network namespace: ${link}`);\n return Number(inode);\n },\n async cgroupOf(node, pid) {\n const text = (await cluster.exec(node, [\"cat\", `/proc/${pid}/cgroup`])).trim();\n const path = /^0::(\\S+)$/m.exec(text)?.[1];\n // an empty or a short path is the root of the node: a write there throttles or freezes each pod of the node\n if (path === undefined || !/\\/cri-containerd-[0-9a-f]{64}\\.scope$/.test(path))\n throw new Error(`the cgroup of ${pid} on ${node} is not the scope of one container: ${text}`);\n return `/proc/1/root/sys/fs/cgroup${path}`;\n },\n async close() {\n await lab.stopProbe();\n await stopOutside();\n if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => undefined);\n watcher = null;\n for (const container of opened.reverse()) await container.stop({ timeout: 0 }).catch(() => undefined);\n },\n };\n return lab;\n};\n\n/** This function pins a Docker image by digest under a local tag that holds the digest, for `kind load` and `imagePullPolicy: Never`. */\nexport const localPin = async (image: string): Promise<string> => {\n const match = /^([^@]+?)(?::([^@:/]+))?@sha256:([0-9a-f]{64})$/.exec(image);\n if (match === null) throw new Error(`${image} is not pinned by a digest`);\n const [, repository, tag, digest] = match;\n const local = `systems-lab/${repository!.split(\"/\").at(-1)}:${tag ?? \"pinned\"}-${digest!.slice(0, 12)}`;\n const present = await command(\"docker\", [\"image\", \"inspect\", \"--format\", \"{{json .RepoDigests}}\", `${repository}@sha256:${digest}`]).catch(() => \"\");\n if (!present.includes(digest!)) await command(\"docker\", [\"pull\", `${repository}@sha256:${digest}`], { timeout: 900_000 });\n await command(\"docker\", [\"tag\", `${repository}@sha256:${digest}`, local]);\n return local;\n};\n","import { createHash } from \"node:crypto\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { KindCell, KubeletProbe, MeshMode, Stack } from \"./cell.ts\";\nimport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\nimport type { VipService } from \"./announcer.ts\";\nimport { announcerOf } from \"./announcer.ts\";\nimport { LB_CLASS, VIP_ANNOTATION } from \"./names.ts\";\nimport { ZONE_LABEL, ZONE_LOST } from \"./scenario.ts\";\n\n/**\n * This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the\n * server as a Deployment behind a Service, and one client on each node. The server is the service\n * or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout\n * strategy and the probe fields state the defaults of Kubernetes as values.\n */\n\n/** This function gives the stack of a cell: the service, when the cell names no stack. */\nexport const stackOf = (cell: Pick<KindCell, \"stack\">): Stack => cell.stack ?? \"service\";\n\nexport { CLIENT, HEALTH_PORT, LOG_LABEL, PORT, REDIS, SERVER } from \"./names.ts\";\n\n/** This image is Redis 7.4, the stock target of the harness, pinned by digest. */\nexport const REDIS_IMAGE = \"redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f\";\n\nconst whole = (amount: Quantity, what: string): number => {\n const seconds = valueIn(amount, \"s\");\n if (!Number.isInteger(seconds) || seconds < 0) throw new RangeError(`${what} is ${seconds} s, not a whole number of seconds`);\n return seconds;\n};\n\n/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */\nexport const namespaceOf = (cell: Pick<KindCell, \"name\">): string =>\n `lab-${createHash(\"sha256\").update(cell.name).digest(\"hex\").slice(0, 10)}`;\n\n/** These are the labels of the namespace of each mode of a mesh. */\nconst MESH_LABELS: Readonly<Record<MeshMode, Readonly<Record<string, string>>>> = {\n sidecar: { \"istio-injection\": \"enabled\" },\n ambient: { \"istio.io/dataplane-mode\": \"ambient\" },\n};\n\n/**\n * This function gives the Namespace object of a cell. In the sidecar mode of a mesh, the label\n * `istio-injection=enabled` makes the injector of Istio put a sidecar in each pod of the namespace.\n * In the ambient mode, the label `istio.io/dataplane-mode=ambient` makes istio-cni redirect the\n * traffic of each pod to ztunnel. The listeners of ztunnel are in the network namespace of the pod.\n */\nexport const namespaceObject = (cell: Pick<KindCell, \"mesh\">, namespace: string): Record<string, unknown> => ({\n apiVersion: \"v1\",\n kind: \"Namespace\",\n metadata: { name: namespace, ...(cell.mesh === undefined ? {} : { labels: { ...MESH_LABELS[cell.mesh.mode] } }) },\n});\n\n/**\n * This function gives a probe of the pod, with each field of the cell. The probe of the service is an\n * HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the\n * kubelet. It is a Check of the service \"\" on the port of the server, and the answer is SERVING until\n * GracefulStop. A gRPC probe can name another service (`service`): the field `service` of `GRPCAction`\n * (`staging/src/k8s.io/api/core/v1/types.go` at v1.37.0, line 2892).\n */\nexport const probeOf = (path: string | null, probe: KubeletProbe, service?: string): Record<string, unknown> => ({\n ...(path === null ? { grpc: service === undefined ? { port: PORT } : { port: PORT, service } } : { httpGet: { path, port: HEALTH_PORT } }),\n initialDelaySeconds: 0,\n periodSeconds: whole(probe.period, `the period of ${path ?? \"the gRPC probe\"}`),\n timeoutSeconds: whole(probe.timeout, `the timeout of ${path ?? \"the gRPC probe\"}`),\n failureThreshold: probe.threshold,\n successThreshold: 1,\n});\n\n/** This variable is the name of the pod, from the API: the service and grpc-go put it in their lines. */\nconst POD_ENV = { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } };\n\n/**\n * This function gives the environment of the app: its config as JSON, and the name of its pod. With\n * `failsRoute`, the route fails while the flag file is there (`cannot-serve`).\n */\nconst appEnv = (cell: KindCell): Array<Record<string, unknown>> => [\n {\n name: \"CONFIG\",\n value: JSON.stringify({\n name: SERVER,\n port: PORT,\n healthPort: HEALTH_PORT,\n declaration: cell.declaration,\n ...(cell.trigger.kind === \"cannot-serve\" && cell.trigger.serves !== true ? { failsRoute: true } : {}),\n }),\n },\n POD_ENV,\n];\n\n/** This file is the flag of the trigger `liveness-restart`, in the root of the server process (`run.ts`, the step `flag`). */\nconst FLAG_FILE = \"/tmp/lab-fail\";\n\n/**\n * This function gives the container of the server of a cell. The service runs `app.mjs` of the lab\n * image with its declaration. The server grpc-go runs from the image of the harness. Its lines name\n * the pod and stamp in milliseconds. Its replies name the pod in the header `x-pod`. Kubernetes\n * expands `$(POD_NAME)` in the command.\n *\n * The seam of `liveness-restart` in grpc-go is its service `liveness`, which the flag file fails\n * (`--fail-file`). Thus for that trigger the liveness probe is a Check of `liveness`. The readiness\n * probe stays a Check of \"\", which the flag file does not fail.\n */\nconst serverContainer = (cell: KindCell, version: number): Record<string, unknown> => {\n const env = { name: \"LAB_VERSION\", value: String(version) };\n if (stackOf(cell) === \"service\")\n return {\n command: [\"node\", \"app.mjs\"],\n env: [...appEnv(cell), env],\n ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(\"/startup\", cell.pod.startup) }),\n readinessProbe: probeOf(\"/readiness\", cell.pod.readiness),\n livenessProbe: probeOf(\"/liveness\", cell.pod.liveness),\n };\n const seam = cell.trigger.kind === \"liveness-restart\";\n return {\n command: [\"grpc-go-server\", \"--port\", String(PORT), \"--name\", \"$(POD_NAME)\", \"--clock\", \"ms\", ...(seam ? [\"--fail-file\", FLAG_FILE] : [])],\n env: [POD_ENV, env],\n ports: [{ containerPort: PORT }],\n ...(cell.pod.startup === null ? {} : { startupProbe: probeOf(null, cell.pod.startup) }),\n readinessProbe: probeOf(null, cell.pod.readiness),\n livenessProbe: seam ? probeOf(null, cell.pod.liveness, \"liveness\") : probeOf(null, cell.pod.liveness),\n };\n};\n\n/** These are the objects of Redis: one replica behind a Service, with no persistence. */\nexport const redisObjects = (namespace: string, image: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: REDIS, namespace, labels: { app: REDIS } },\n spec: {\n replicas: 1,\n selector: { matchLabels: { app: REDIS } },\n template: {\n metadata: { labels: { app: REDIS } },\n spec: {\n terminationGracePeriodSeconds: 1,\n containers: [\n {\n name: REDIS,\n image,\n imagePullPolicy: \"Never\",\n command: [\"redis-server\", \"--save\", \"\", \"--appendonly\", \"no\"],\n ports: [{ containerPort: 6379 }],\n readinessProbe: { tcpSocket: { port: 6379 }, periodSeconds: 1, failureThreshold: 1, successThreshold: 1, timeoutSeconds: 1 },\n },\n ],\n },\n },\n },\n },\n {\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: REDIS, namespace },\n spec: { selector: { app: REDIS }, ports: [{ port: 6379, targetPort: 6379 }] },\n },\n];\n\n/**\n * This function gives the preStop hook of a sleep, or nothing for no sleep. The hook is the native\n * sleep action of the kubelet. The API server refuses a sleep action that is longer than the grace\n * (`validateSleepAction`, `pkg/apis/core/validation/validation.go` at v1.37.0, line 3483). Thus a\n * longer sleep is a command in the container, `sleep` from `/bin/sh`, as public manifests write it.\n * The kubelet starts that command with no timeout (`pkg/kubelet/lifecycle/handlers.go`, line 81).\n */\nconst preStopOf = (preStop: number, grace: number): Record<string, unknown> => {\n if (preStop === 0) return {};\n if (preStop <= grace) return { lifecycle: { preStop: { sleep: { seconds: preStop } } } };\n return { lifecycle: { preStop: { exec: { command: [\"/bin/sh\", \"-c\", `sleep ${preStop}`] } } } };\n};\n\n/** This affinity keeps a pod in the zone that a zone loss keeps. */\nconst keptZone = {\n nodeAffinity: {\n requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [{ matchExpressions: [{ key: ZONE_LABEL, operator: \"NotIn\", values: [ZONE_LOST] }] }] },\n },\n};\n\n/**\n * This function gives the Service of a VIP (`announcer.ts`). It is of type LoadBalancer, with the\n * class of its announcer, if the announcer has one. The annotation of the announcer has the address\n * of the VIP. Without `service`, the announcer is kube-vip. The policy `Cluster` lets each node\n * send a request to a replica on another node. The holder writes the address to the status of the\n * Service. Then kube-proxy has the rules of the address on each node.\n */\nexport const vipService = (namespace: string, vip: string, ports: readonly unknown[], service: VipService = { loadBalancerClass: LB_CLASS, annotation: VIP_ANNOTATION }): unknown => ({\n apiVersion: \"v1\",\n kind: \"Service\",\n metadata: { name: SERVER, namespace, annotations: { [service.annotation]: vip } },\n spec: {\n type: \"LoadBalancer\",\n ...(service.loadBalancerClass === null ? {} : { loadBalancerClass: service.loadBalancerClass }),\n externalTrafficPolicy: \"Cluster\",\n selector: { app: SERVER },\n ports,\n },\n});\n\n/**\n * These are the objects of the server: a Service of type ClusterIP, and the Deployment with the\n * settings of the cell. The image is the lab image for the service, or the image of grpc-go.\n *\n * With a VIP (`KindCell.vip`), the Service is of type LoadBalancer, with the address `vip` and the\n * class of its announcer (`vipService`). No replica is in the lost zone. Thus a zone loss takes away\n * only the holder of the VIP.\n */\nexport const serverObjects = (cell: KindCell, namespace: string, image: string, version = 1, vip: string | null = null): unknown[] => {\n const preStop = whole(cell.pod.preStop, \"the preStop sleep\");\n const grace = whole(cell.pod.grace, \"the grace period\");\n if (cell.vip !== undefined && vip === null) throw new Error(`${cell.name}: a cell with a VIP needs the address of the VIP`);\n const ports = [{ name: stackOf(cell) === \"grpc\" ? \"grpc\" : \"http\", port: 80, targetPort: PORT }];\n const zoneLoss = cell.trigger.kind === \"zone-loss\";\n return [\n vip === null\n ? { apiVersion: \"v1\", kind: \"Service\", metadata: { name: SERVER, namespace }, spec: { selector: { app: SERVER }, ports } }\n : vipService(namespace, vip, ports, announcerOf(cell).service),\n {\n apiVersion: \"apps/v1\",\n kind: \"Deployment\",\n metadata: { name: SERVER, namespace, labels: { app: SERVER } },\n spec: {\n replicas: cell.pod.replicas,\n selector: { matchLabels: { app: SERVER } },\n strategy: { type: \"RollingUpdate\", rollingUpdate: { maxSurge: \"25%\", maxUnavailable: \"25%\" } },\n minReadySeconds: 0,\n template: {\n metadata: { labels: { app: SERVER, [LOG_LABEL]: \"1\", \"lab.systems/version\": String(version) } },\n spec: {\n terminationGracePeriodSeconds: grace,\n enableServiceLinks: true,\n topologySpreadConstraints: [\n {\n maxSkew: 1,\n topologyKey: \"kubernetes.io/hostname\",\n whenUnsatisfiable: \"ScheduleAnyway\",\n labelSelector: { matchLabels: { app: SERVER } },\n },\n // a zone loss needs a replica in each zone: the scheduler must keep the spread\n ...(zoneLoss && cell.vip === undefined\n ? [{ maxSkew: 1, topologyKey: ZONE_LABEL, whenUnsatisfiable: \"DoNotSchedule\", labelSelector: { matchLabels: { app: SERVER } } }]\n : []),\n ],\n // with a VIP, the replicas are only in the zone that stays: the loss takes away only the holder of the VIP\n ...(zoneLoss && cell.vip !== undefined ? { affinity: keptZone } : {}),\n containers: [\n {\n name: \"app\",\n image,\n imagePullPolicy: \"Never\",\n ...serverContainer(cell, version),\n ...preStopOf(preStop, grace),\n },\n ],\n },\n },\n },\n },\n ];\n};\n\n/**\n * These are the objects of the clients: a DaemonSet with one client on each node, the control plane\n * too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.\n * Each one is in the lab image, and each one writes the same lines. A service with the protocol\n * `h2c` gives its clients `PROTOCOL`, thus they speak HTTP/2 to it. A workload with a session check\n * gives its clients `SESSION_CHECK`: the knobs of the check, as JSON.\n */\nexport const clientObjects = (cell: KindCell, namespace: string, image: string, target: string): unknown[] => [\n {\n apiVersion: \"apps/v1\",\n kind: \"DaemonSet\",\n metadata: { name: CLIENT, namespace, labels: { app: CLIENT } },\n spec: {\n selector: { matchLabels: { app: CLIENT } },\n template: {\n metadata: { labels: { app: CLIENT, [LOG_LABEL]: \"1\" } },\n spec: {\n terminationGracePeriodSeconds: 0,\n tolerations: [{ key: \"node-role.kubernetes.io/control-plane\", operator: \"Exists\", effect: \"NoSchedule\" }],\n // the clients of a zone loss are only in the zone that stays: they see the loss, and they do not have it\n ...(cell.trigger.kind === \"zone-loss\" ? { affinity: keptZone } : {}),\n containers: [\n {\n name: CLIENT,\n image,\n imagePullPolicy: \"Never\",\n command: [\"node\", stackOf(cell) === \"grpc\" ? \"client-grpc.mjs\" : \"client.mjs\"],\n env: [\n { name: \"TARGET\", value: target },\n { name: \"EVERY_MS\", value: String(valueIn(cell.workload.every, \"ms\")) },\n { name: \"TIMEOUT_MS\", value: String(valueIn(cell.workload.timeout, \"ms\")) },\n ...(cell.workload.connections === undefined ? [] : [{ name: \"CONNECTIONS\", value: String(cell.workload.connections) }]),\n ...(cell.declaration?.protocol === \"h2c\" ? [{ name: \"PROTOCOL\", value: \"h2c\" }] : []),\n ...(cell.workload.sessionCheck === undefined ? [] : [{ name: \"SESSION_CHECK\", value: JSON.stringify(cell.workload.sessionCheck) }]),\n { name: \"POD_NAME\", valueFrom: { fieldRef: { fieldPath: \"metadata.name\" } } },\n { name: \"NODE_NAME\", valueFrom: { fieldRef: { fieldPath: \"spec.nodeName\" } } },\n ],\n },\n ],\n },\n },\n },\n },\n];\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Poll, Stage, Timing } from \"@mark1russell7/service\";\nimport { failSpan, recoverSpan } from \"@mark1russell7/service\";\nimport { bandOf, seconds, secondsOf } from \"./bands.ts\";\nimport type { Band, Declaration, KubeletProbe, Reference, Workload } from \"./cell.ts\";\n\n/**\n * This module is the arithmetic of the predictions. Each band is a range of times after a\n * reference event of the run. Each term comes from a declaration, from the pod settings, or from\n * the calibration of the propagation. No term comes from a default. The arithmetic operates in\n * seconds, and each result is a quantity.\n */\n\nconst s = (amount: Quantity): number => valueIn(amount, \"s\");\n/** This function rounds a time to the microsecond, thus a band has no float residue. */\nconst us = (value: number): number => Math.round(value * 1e6) / 1e6;\nconst band = (lo: number, hi: number): Band => bandOf(us(lo), us(hi));\n\n/**\n * A propagation is the time from a change of an endpoint to the rules of each node. A\n * calibration measures it for each kube-proxy mode and node count. Without a calibration, its upper\n * bound is `minSyncPeriod`: an idle kube-proxy syncs a change at once, or one `minSyncPeriod`\n * after its last sync.\n */\nexport interface Propagation extends Band {\n /** This text tells where the bound comes from: a calibration, or the documented `minSyncPeriod`. */\n readonly from: string;\n}\n\n/** This propagation is the documented bound, before a calibration: from zero to one `minSyncPeriod` of 1 s. */\nexport const DOCUMENTED: Propagation = { lo: seconds(0), hi: seconds(1), from: \"minSyncPeriod (1 s), not calibrated\" };\n\n/**\n * This function gives the time from a flip of the probed state to the decision of the kubelet. The\n * next probe comes within one period, then `threshold − 1` more periods follow. A probe of an\n * endpoint that does not answer fails at its timeout.\n */\nexport const kubeletBand = (probe: KubeletProbe, hangs: boolean): Band => {\n const late = hangs ? s(probe.timeout) : 0;\n return band((probe.threshold - 1) * s(probe.period) + late, probe.threshold * s(probe.period) + late);\n};\n\n/**\n * This function gives the time from a fault of a dependency to the trip of a readiness or liveness\n * latch. A dependency that refuses fails each poll at once. A silent dependency fails each poll at\n * the request timeout of its client, or at the timeout of the poll when that is shorter. The\n * watchdog of the client can close the connection sooner, and then each poll fails at once.\n */\nexport const latchBand = (\n timing: Timing,\n poll: Poll,\n failure: \"refuses\" | \"silent\",\n requestTimeout?: Quantity,\n): Band => {\n const error = failSpan(timing, poll, \"error\");\n if (failure === \"refuses\") return band(s(error.min), s(error.max));\n const wait = requestTimeout === undefined ? s(poll.timeout) : Math.min(s(requestTimeout), s(poll.timeout));\n const late = failSpan(timing, { ...poll, timeout: seconds(wait) }, \"timeout\");\n return band(s(error.min), s(late.max));\n};\n\n/** This function gives the time from a fault to the removal of the endpoints from the rules: the latch, then the kubelet, then the propagation. */\nexport const removalBand = (latch: Band, probe: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(probe, false));\n const trip = secondsOf(latch);\n const spread = secondsOf(propagation);\n return band(trip.lo + kubelet.lo + spread.lo, trip.hi + kubelet.hi + spread.hi);\n};\n\n/**\n * This function gives the time from a fault to the SIGTERM of a replica whose liveness fails with\n * its readiness. The time is the latch, then the liveness probes of the kubelet, then the preStop sleep.\n */\nexport const restartBand = (latch: Band, liveness: KubeletProbe, preStop: Quantity): Band => {\n const kubelet = secondsOf(kubeletBand(liveness, false));\n const trip = secondsOf(latch);\n return band(trip.lo + kubelet.lo + s(preStop), trip.hi + kubelet.hi + s(preStop));\n};\n\n/**\n * This function gives the time from the start of a stall of the event loop to the removal of the\n * replica from the rules. Each probe in the stall fails at its timeout, because the loop does not answer it.\n */\nexport const stallRemovalBand = (readiness: KubeletProbe, propagation: Propagation): Band => {\n const kubelet = secondsOf(kubeletBand(readiness, true));\n const spread = secondsOf(propagation);\n return band(kubelet.lo + spread.lo, kubelet.hi + spread.hi);\n};\n\n/**\n * A node lifecycle is the set of timers that removes the pods of a lost node. The same timers give\n * them back after the heal of the node. The lab sets none of them, thus each one is a default of Kubernetes\n * v1.37.0 (file and line in that tag).\n */\nexport interface NodeLifecycle {\n /**\n * This time is `NodeMonitorGracePeriod`. The node lifecycle controller marks a node Unknown when\n * it saw no renewal of its lease for this time. Then it marks each pod of the node not ready.\n */\n readonly grace: Quantity;\n /** This time is `NodeMonitorPeriod`: the controller checks each node once in it. */\n readonly monitor: Quantity;\n /** This time is the renewal interval of the lease of the kubelet. */\n readonly renew: Quantity;\n /** This share is the jitter of each renewal interval: a renewal comes `renew` to `renew × (1 + jitter)` after the one before it. */\n readonly jitter: number;\n /** This time is the margin of the writes of the controller: the status of the node, then each pod of the node. */\n readonly writes: Quantity;\n /** This time is the PING timeout of the HTTP/2 health check of client-go. A kubelet that gets no PING ACK in it connects again. */\n readonly pingTimeout: Quantity;\n /** This time is the longest first backoff of a reflector of client-go before it watches again. */\n readonly rewatch: Quantity;\n /** This time is the sync period of the status manager of the kubelet: it puts back a pod status that the controller changed. */\n readonly statusSync: Quantity;\n}\n\n/**\n * These are the defaults of Kubernetes v1.37.0 for a lost node:\n * - `grace` 50 s and `monitor` 5 s: pkg/controller/nodelifecycle/config/v1alpha1/defaults.go, lines\n * 46 and 52. The controller checks the nodes on a sliding period (node_lifecycle_controller.go,\n * lines 506–510). It sets the probe time when it sees a new lease (lines 952–960), and it marks the\n * node Unknown after `grace` (line 962). Then it marks each pod of the node not ready (lines 768–773).\n * - `renew` 10 s: `NodeLeaseDurationSeconds` 40 (pkg/kubelet/apis/config/v1beta1/defaults.go, line 150)\n * times the fraction 0.25 (pkg/kubelet/kubelet.go, lines 237 and 1156).\n * - `jitter` 0.04: the lease controller renews on a sliding jitter of 0.04\n * (staging/src/k8s.io/component-helpers/apimachinery/lease/controller.go, line 153).\n * - `pingTimeout` 15 s: the PING timeout of the HTTP/2 health check, after a read idle timeout of 30 s\n * (staging/src/k8s.io/apimachinery/pkg/util/net/http.go, lines 131–192).\n * - `rewatch` 1.6 s: a first backoff of 800 ms with a jitter of 1.0\n * (staging/src/k8s.io/client-go/tools/cache/reflector.go, lines 62–67).\n * - `statusSync` 10 s: the sync period of the status manager (pkg/kubelet/status/status_manager.go, line 215).\n * - `writes` 0.25 s is a margin of the lab, not a default.\n */\nexport const NODE_LIFECYCLE: NodeLifecycle = {\n grace: seconds(50),\n monitor: seconds(5),\n renew: seconds(10),\n jitter: 0.04,\n writes: seconds(0.25),\n pingTimeout: seconds(15),\n rewatch: seconds(1.6),\n statusSync: seconds(10),\n};\n\n/**\n * This function gives the time from the loss of a node to the drop of its pods from the rules of\n * the other nodes. The last renewal of the lease comes up to `renew × (1 + jitter)` before the loss.\n * The controller sees it up to one `monitor` later, and marks the node `grace` after that. Then the\n * writes and the propagation follow. The band does not add the low bound of the propagation.\n */\nexport const nodeLossBand = (lifecycle: NodeLifecycle, propagation: Propagation): Band => {\n const last = s(lifecycle.renew) * (1 + lifecycle.jitter);\n return band(s(lifecycle.grace) - last, s(lifecycle.grace) + s(lifecycle.monitor) + s(lifecycle.writes) + s(propagation.hi));\n};\n\n/**\n * This function gives the time from the heal of a lost node until its pods are ready again in the\n * EndpointSlice. The kubelet connects again in one PING timeout at the most: its old connection\n * waits behind the backoff of TCP. Its reflectors watch again after their first backoff. Then its\n * status manager puts back the pod status in one sync period at the most.\n */\nexport const nodeReturnBand = (lifecycle: NodeLifecycle): Band =>\n band(0, s(lifecycle.pingTimeout) + s(lifecycle.rewatch) + s(lifecycle.statusSync) + s(lifecycle.writes));\n\n/** This function gives the cap of a stage in a declaration, or zero when the declaration does not name the stage. */\nexport const capOf = (declaration: Declaration, stage: Stage): Quantity => {\n const found = declaration.phases.find(([name]) => name === stage);\n return seconds(found === undefined ? 0 : s(found[1]));\n};\n\n/**\n * This function gives the shortest time from the deletion of a pod to the stop of its intake. It\n * is the preStop sleep, then the hold of `fail readiness`. The kubelet adds its own delay before\n * the preStop, thus the real time is longer.\n */\nexport const intakeAfter = (declaration: Declaration, preStop: Quantity): Quantity =>\n seconds(us(s(preStop) + s(capOf(declaration, \"fail readiness\"))));\n\n/**\n * A deletion window is what a deletion predicts on one node. The listener of the pod closes at the\n * stop of its intake. The rules of the node stop the new connections to the pod at the\n * propagation. A connection between the two is refused. Thus the window is empty when the intake\n * stops after the latest propagation.\n */\nexport interface DeletionWindow {\n /** This flag tells that no connection can meet a closed listener. */\n readonly empty: boolean;\n /** This band is the end of the window after the deletion: the propagation. */\n readonly end: Band;\n /** This time is the earliest stop of the intake after the deletion. */\n readonly intake: Quantity;\n}\n\n/** This function gives the deletion window of a declaration and a preStop sleep on a node with a propagation. */\nexport const deletionWindow = (declaration: Declaration, preStop: Quantity, propagation: Propagation): DeletionWindow => {\n const intake = intakeAfter(declaration, preStop);\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= spread.hi, end: band(spread.lo, spread.hi), intake };\n};\n\n/**\n * This function gives the window on a node whose kube-proxy pauses across a deletion. The pause\n * starts `lead` before the deletion and holds for `hold`. The node then syncs in one propagation\n * after the end of the pause, at `resume` after the deletion. The window is empty when the intake\n * stops after that.\n */\nexport const pausedWindow = (\n declaration: Declaration,\n preStop: Quantity,\n lead: Quantity,\n hold: Quantity,\n propagation: Propagation,\n): DeletionWindow & { readonly resume: Quantity } => {\n const intake = intakeAfter(declaration, preStop);\n const resume = us(s(hold) - s(lead));\n const spread = secondsOf(propagation);\n return { empty: s(intake) >= resume + spread.hi, end: band(spread.lo, spread.hi), intake, resume: seconds(resume) };\n};\n\n/**\n * A learn window is what `cannot-serve` predicts: when the callers stop getting errors from a\n * replica that cannot serve. The window starts at the fault, and its end comes from `end.from`.\n */\nexport interface LearnWindow {\n /**\n * This time is the interval between two requests on one connection of a client: `every` times the\n * connections. A client of HTTP/2 sends its streams on fewer connections, thus its turn is shorter.\n */\n readonly turn: Quantity;\n /** This band is the trip of the readiness latch of the service after the fault. */\n readonly latch: Band;\n /** This band is the first failed request after the fault: within one turn. */\n readonly start: Band;\n readonly end: { readonly from: Reference; readonly band: Band };\n}\n\n/**\n * This function gives the learn window of a declaration, its readiness probe, a propagation and a\n * workload of keep-alive connections. A caller selects a replica only when it opens a connection:\n * - The lame duck (`lameDuck` true or no value): after the latch, each answer closes its connection,\n * thus each connection of the replica gets one more failure and opens again. The rules select the\n * replica until the removal, thus the last failure comes in one turn after it.\n * - The signal of the lame duck is `Connection: close` on HTTP/1.1, and GOAWAY on HTTP/2. The\n * arithmetic is the same: the turn is `every` times the connections, which are sessions on HTTP/2.\n * - No lame duck: no connection opens again, thus the rules do not matter. The failures end only\n * when the readiness of the service recovers after the heal, and one turn after it.\n */\nexport const learnWindow = (declaration: Declaration, readiness: KubeletProbe, propagation: Propagation, workload: Workload): LearnWindow => {\n const turn = us(s(workload.every) * (workload.connections ?? 1));\n const latch = latchBand(declaration.readiness, declaration.poll, \"refuses\");\n const start = band(0, turn);\n if (declaration.lameDuck === false) {\n const recover = recoverSpan(declaration.readiness, declaration.poll);\n return { turn: seconds(turn), latch, start, end: { from: \"heal\", band: band(0, s(recover.max) + turn) } };\n }\n const removal = secondsOf(removalBand(latch, readiness, propagation));\n return { turn: seconds(turn), latch, start, end: { from: \"fault\", band: band(secondsOf(latch).lo, removal.hi + turn) } };\n};\n\n/**\n * The backlog term of a stall (SYS-42) is in `bands.ts`, thus the judge reads it with no other\n * import. `backlogEnd` adds n × d to the end band. `requestBand` counts the failed requests from\n * the measured period. A window carries the term in `backlog` (`BacklogTerm`).\n */\nexport type { CountBand } from \"./bands.ts\";\nexport { backlogEnd, drainBand, overshootBand, requestBand, showBand } from \"./bands.ts\";\n","import { median } from \"@mark1russell7/rigor-statistics\";\n\n/**\n * The quiet gate asks for an idle share of the VM before a run. The clients of the cell run at that\n * time, and the agent of each node reads the whole conntrack table each second. Thus the idle share\n * has the load of the cell in it. A flat share can void a heavy cell with no other load (SYS-95).\n * The gate reads the cell against itself. The baseline is the idle share of the settled cell, and\n * a foreign load shows as a fall from the baseline.\n */\n\n/** These numbers are the terms of the gate. Each share is a share of all the CPUs of the VM. */\nexport interface QuietGate {\n /** This share is enough for each cell, as it was before SYS-95. */\n readonly flat: number;\n /** This share is the most fall from the baseline that the gate lets through. */\n readonly margin: number;\n /** This share is the least that the gate lets through, with any baseline. */\n readonly floor: number;\n /** This time in milliseconds is the age of a cell at its first baseline sample. */\n readonly settle: number;\n /** This count is the samples of a baseline. */\n readonly samples: number;\n}\n\n/**\n * These are the terms of the lab. The flat share is the share of SYS-42. The other terms come from the\n * calibration of SYS-95, over the 9 records of SYS-57, with no foreign container in them:\n *\n * - The margin is the 99th percentile of the fall from the settled median of an environment: 4.88\n * points of 93 samples, rounded up.\n * - The floor is the lowest settled median of an environment, 48 %, minus the margin.\n * - The time to settle holds the slowest environment. Its conntrack table was at its plateau at 105 s.\n */\nexport const QUIET_GATE: QuietGate = { flat: 0.6, margin: 0.05, floor: 0.43, settle: 120_000, samples: 3 };\n\n/**\n * This function gives the idle share that a run needs. With no baseline, it is the floor: a warm-up\n * counts for nothing, and its table is not full yet. A cell that is at rest at the flat share or more\n * keeps the flat share.\n */\nexport const neededIdle = (gate: QuietGate, baseline: number | null): number =>\n baseline === null ? gate.floor : Math.min(gate.flat, Math.max(gate.floor, baseline - gate.margin));\n\n/** This function gives the reason of a sample that the gate refuses. The site reads it back with `NOT_QUIET`. */\nexport const quietWhy = (idle: number, needed: number, foreign: readonly string[]): string =>\n `${Math.round(idle * 100)}% idle, the gate needs ${Math.round(needed * 100)}%, other lab containers: ${foreign.join(\"; \") || \"none\"}`;\n\n/**\n * This form reads the void of a run that the gate refuses. Its groups are the wait in seconds, the idle share, the share\n * of the gate and the other lab containers. A record from before SYS-95 has no share of the gate.\n */\nexport const NOT_QUIET: RegExp = /^the VM was not quiet in (\\d+) s: (\\d+)% idle(?:, the gate needs (\\d+)%)?, other lab containers: (.*)$/;\n\n/**\n * This function gives the next baseline of a cell from the samples of one environment. Samples with\n * a foreign container do not count. The highest median holds, because a foreign load only lowers\n * the idle share. Thus a load in one environment does not move the gate of the other one.\n */\nexport const baselineOf = (previous: number | null, samples: ReadonlyArray<{ readonly idle: number; readonly foreign: readonly string[] }>): number | null => {\n const clean = samples.filter((sample) => sample.foreign.length === 0).map((sample) => sample.idle);\n if (clean.length === 0) return previous;\n const middle = median(clean);\n return previous === null ? middle : Math.max(previous, middle);\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Finding, Scenario, ServiceDescription, StopWay, Verdict } from \"@mark1russell7/service\";\nimport { lint, lintStop } from \"@mark1russell7/service\";\nimport { redisClient } from \"@mark1russell7/service/redis\";\nimport { DRAIN_RULES } from \"@mark1russell7/service/rules\";\nimport type { KindCell } from \"./cell.ts\";\nimport { compose } from \"./compose.ts\";\nimport type { Propagation } from \"./predict.ts\";\n\n/**\n * The rules of the ordering law that a drain in Kubernetes meets are data in the entry\n * `@mark1russell7/service/rules` (SYS-32). That entry loads no Testcontainers, thus a service can\n * import the rules without kind. This module gives them again, thus the exports of kind stay the\n * same. A kind cell gets the rules that guard the way that its plan stops a pod.\n */\nexport { DRAIN_RULES, O04, O09, O10, O16 } from \"@mark1russell7/service/rules\";\n\nconst seconds = (amount: Quantity): number => valueIn(amount, \"s\");\nconst s = (value: number): Quantity => ({ value, unit: \"s\" });\n\n/**\n * This function gives the scenario of a cell for `lint()`: the preStop sleep, the grace, the probe\n * period and the propagation. The probe windows are the period times the threshold of each probe.\n */\nexport const scenarioOf = (cell: KindCell, propagation: Propagation): Scenario => ({\n platform: {\n \"prestop_sleep@pod\": cell.pod.preStop,\n \"termination_grace@pod\": cell.pod.grace,\n \"probe_period@pod\": cell.pod.readiness.period,\n \"propagation_bound@cluster\": propagation.hi,\n },\n probes: {\n readiness: s(seconds(cell.pod.readiness.period) * cell.pod.readiness.threshold),\n liveness: s(seconds(cell.pod.liveness.period) * cell.pod.liveness.threshold),\n },\n});\n\n/** A verdict set is the verdict of each drain rule for one declaration, by the identifier of the rule. */\nexport type Verdicts = Readonly<Record<string, Verdict>>;\n\n/**\n * This function gives the way that the fault plan of a cell stops a pod, or null when the plan stops no pod.\n * - A deletion, a rolling update, a node drain, a scale-in and a rollout delete a pod with its grace.\n * - In T07 a pause of kube-proxy comes before a deletion. In T18 the pause is on Redis.\n * - The crash of the lab is SIGKILL from the node. Like a crash, it has no preStop hook and no drain.\n * - The other faults are on a link, on Redis, on the DNS of the clients or on the process. The kubelet can restart a pod after them.\n * A zone loss freezes the pods of its node and deletes none of them in its hold.\n */\nexport const stopOf = (cell: KindCell): StopWay | null => {\n switch (cell.trigger.kind) {\n case \"pod-deletion\":\n case \"rolling-update\":\n case \"node-drain\":\n case \"scale-in\":\n case \"rollout\":\n return \"deletion\";\n case \"freeze-process\":\n return cell.template === \"T07\" ? \"deletion\" : null;\n case \"crash\":\n return \"crash\";\n case \"liveness-restart\":\n return \"liveness-restart\";\n case \"rst\":\n case \"blackhole\":\n case \"peer-gone-without-close\":\n case \"app-stall\":\n case \"cpu-throttle\":\n case \"authorization-policy\":\n case \"idle\":\n case \"ztunnel-restart\":\n case \"certificate-expiry\":\n case \"cannot-serve\":\n case \"zone-loss\":\n case \"latency\":\n case \"dns-blackhole\":\n return null;\n }\n};\n\n/**\n * This function applies the drain rules to the description of a cell, and gives each finding. A cell\n * whose plan stops a pod gets the rules of that way (`lintStop()`). A plan that stops no pod gets each\n * drain rule, because a restart by the kubelet or a later deletion can follow its fault.\n */\nexport const lintCell = (description: ServiceDescription, cell: KindCell, propagation: Propagation): Finding[] => {\n const way = stopOf(cell);\n const scenario = scenarioOf(cell, propagation);\n return way === null ? lint(description, scenario, DRAIN_RULES) : lintStop(description, scenario, DRAIN_RULES, way);\n};\n\n/** This function gives the verdict of each drain rule, by its identifier. */\nexport const verdictsOf = (findings: readonly Finding[]): Verdicts =>\n Object.fromEntries(findings.map((finding) => [finding.rule, finding.verdict]));\n\n/** This function gives the description of the service of a cell, as the app of the image composes it. Nothing connects. */\nexport const describeCell = (cell: KindCell): ServiceDescription => {\n if (cell.declaration === null) throw new Error(`${cell.name}: the server is not the service, thus the cell has no declaration to describe`);\n const redis = cell.declaration.redis;\n const client =\n redis === null\n ? undefined\n : redisClient(\"redis\", { host: \"redis\", port: 6379, system: \"systems.component.redis\", knobs: redis.knobs, heartbeatMisses: redis.misses });\n return compose(\"lab\", cell.declaration, { redis: client }).describe();\n};\n","import { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport { imageOf, substrateKey, substrateOf, substrateOn } from \"@mark1russell7/harness\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { Cycle, KindCell, KindRun, Replica } from \"./cell.ts\";\nimport type { CiliumImages } from \"./cilium.ts\";\nimport { CILIUM_IMAGES, CILIUM_VERSION, installCilium } from \"./cilium.ts\";\nimport type { Cluster } from \"./cluster.ts\";\nimport { command, createCluster } from \"./cluster.ts\";\nimport type { Step, Target } from \"./faults.ts\";\nimport { NODE, POD, POD_LINK, VERSION, deletes as deletesAPod, dnsCommands, faultSpan, middleboxCommands, middleboxRemoval, placementOf, planOf, sysctlCommand } from \"./faults.ts\";\nimport { labImage } from \"./image.ts\";\nimport type { MeshImages } from \"./istio.ts\";\nimport { EDS_EVERY, ISTIO_IMAGES, LDS_EVERY, MESH_IMAGES, followsZtunnel, installMesh, meshKey, pollsListeners, serverCluster, setZtunnelEnv, ztunnelCertificates } from \"./istio.ts\";\nimport { KUBE_VIP_ANNOUNCER, METALLB_ANNOUNCER, announcerOf, macsAt } from \"./announcer.ts\";\nimport { vipNetworkOf, vipServed } from \"./kubevip.ts\";\nimport { METALLB_NAMESPACE } from \"./metallb.ts\";\nimport type { KindLab, Line, Stamp } from \"./lab.ts\";\nimport { localPin, openKindLab } from \"./lab.ts\";\nimport { OUTSIDE } from \"./names.ts\";\nimport { PRODUCER, PULSAR_IMAGE, PULSAR_NAMESPACE, PULSAR_VERSION, holdPulsar, installPulsar, pulsarObjects, pulsarRecord } from \"./pulsar.ts\";\nimport type { QuietGate } from \"./quiet.ts\";\nimport { QUIET_GATE, baselineOf, neededIdle, quietWhy } from \"./quiet.ts\";\nimport { CLIENT, REDIS, REDIS_IMAGE, SERVER, clientObjects, namespaceObject, namespaceOf, redisObjects, serverObjects, stackOf } from \"./manifests.ts\";\nimport type { PodLine, Request } from \"./observe.ts\";\nimport { byClients, ztunnelReplaced } from \"./observe.ts\";\nimport type { EnvironmentManifest, KindCellRecord, KindCellResult, LoadSample } from \"./record.ts\";\nimport { FORMAT } from \"./record.ts\";\nimport { scenario } from \"./scenario.ts\";\nimport { judgeKind, observationAfter, observationEnd } from \"./verdict.ts\";\n\n/** This time is the interval of the poll of the rules on each node. */\nexport const RULES_EVERY: Quantity = { value: 50, unit: \"ms\" };\n\n/** These options run a catalog of kind cells. */\nexport interface RunOptions {\n /** This folder gets the raw record of each cell and `summary.ndjson`. */\n readonly out: string;\n readonly log: (line: string) => void;\n /** This count is the number of environments, each a new cluster. */\n readonly environments?: number;\n /** This share of the CPUs of the VM must be idle before a run starts. */\n readonly idle?: number;\n /**\n * This folder holds the run folder of each cluster: its config, its kubeconfig and the cache of\n * kubectl. It is not the output, because a kubeconfig holds credentials.\n */\n readonly work?: string;\n}\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst slug = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n/** This function tells if a cell has Redis: only a declaration of the service can have it. */\nconst hasRedis = (cell: KindCell): boolean => cell.declaration !== null && cell.declaration.redis !== null;\n\n/** These are the images of a cluster: the lab, Redis, grpc-go for the stack `grpc`, and Istio for a mesh. */\ninterface Images {\n readonly lab: string;\n readonly redis: string;\n /** This image is the local image of Pulsar, for the producers of a cell with a broker. */\n readonly pulsar?: string | undefined;\n readonly grpc?: string | undefined;\n readonly mesh?: MeshImages | undefined;\n}\n\n/**\n * This function gives the key of the cluster of a cell: its kube-proxy mode, its node count, and the\n * flavor of its mesh. A zone loss has clusters of its own, with a zone on each node.\n */\nconst clusterKey = (cell: KindCell): string =>\n (cell.mesh !== undefined ? `${cell.mode}-${cell.workers}-${cell.mesh.flavor}` : zoned(cell) ? `${cell.mode}-${cell.workers}-zones` : `${cell.mode}-${cell.workers}`) +\n // a cell with a VIP gets a cluster with its announcer: kube-vip, or MetalLB (\"mlb\", as a kind name has 32 characters at most).\n // Settings of memberlist get a cluster of their own: the probe interval in ms and the multiplier of the suspicion\n (cell.vip === undefined\n ? \"\"\n : cell.vip.announcer !== \"metallb\"\n ? \"-vip\"\n : cell.vip.memberlist === undefined\n ? \"-vip-mlb\"\n : `-mlb${valueIn(cell.vip.memberlist.probeInterval, \"ms\")}x${cell.vip.memberlist.suspicionMult}`) +\n // a cell with a broker gets a cluster with Pulsar\n (cell.pulsar === undefined ? \"\" : \"-pul\");\n\n/** This function gives the name of the cluster of a cell in an environment. Kind permits 32 characters at most: lowercase letters, digits and dashes. */\nexport const clusterNameOf = (cell: KindCell, env: number): string => `lab-${clusterKey(cell)}-${env}`;\n\n/** This function tells if a cell needs a cluster with zones. A zone loss needs one, and so does a cell with settings of memberlist, that shares the cluster of its zone loss. */\nconst zoned = (cell: KindCell): boolean => cell.trigger.kind === \"zone-loss\" || cell.vip?.memberlist !== undefined;\n\n/**\n * This tracker keeps the latest state of the cell from the lines: each pod, each slice and each\n * client. It also has, for each node, the backends of its rules and the hosts of its client sidecar.\n */\nclass Tracker {\n readonly pods = new Map<string, PodLine>();\n readonly slices = new Map<string, Line>();\n readonly backends = new Map<string, readonly string[]>();\n readonly hosts = new Map<string, readonly string[]>();\n readonly clients = new Map<string, { node: string; ok: number; failed: number }>();\n /** This map is the backend of the last answer of each client, by client. */\n readonly answers = new Map<string, string>();\n /** This map is the last send of each producer of a broker that got an answer, and the last that failed. */\n readonly sends = new Map<string, { ok: number; failed: number }>();\n /** This map is the last lookup of the owner of the topic of each producer, and the host that it names. */\n readonly owners = new Map<string, { t: number; owner: string | null }>();\n last = 0;\n /** This time is the last line of a producer. */\n lastSend = 0;\n see = (line: Line): void => {\n if (typeof line.t === \"number\" && line.t > this.last && line.ev === \"req\") this.last = line.t;\n if (line.ev === \"pod\") {\n const pod = line as unknown as PodLine;\n if (pod.type === \"DELETED\") this.pods.delete(pod.name);\n else this.pods.set(pod.name, pod);\n } else if (line.ev === \"slice\") {\n if (line[\"type\"] === \"DELETED\") this.slices.delete(String(line[\"name\"]));\n else this.slices.set(String(line[\"name\"]), line);\n } else if (line.ev === \"rules\") {\n this.backends.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"eds\") {\n this.hosts.set(String(line[\"node\"]), line[\"b\"] as readonly string[]);\n } else if (line.ev === \"req\") {\n const request = line as unknown as Request;\n const client = this.clients.get(request.p) ?? { node: request.n, ok: 0, failed: 0 };\n if (request.o === \"ok\") client.ok = Math.max(client.ok, request.t);\n else client.failed = Math.max(client.failed, request.t);\n this.clients.set(request.p, client);\n if (request.o === \"ok\" && request.b !== undefined) this.answers.set(request.p, request.b);\n } else if (line.ev === \"send\") {\n const pod = String(line[\"p\"]);\n const send = this.sends.get(pod) ?? { ok: 0, failed: 0 };\n if (line[\"o\"] === \"ok\") send.ok = Math.max(send.ok, line.t);\n else send.failed = Math.max(send.failed, line.t);\n this.sends.set(pod, send);\n this.lastSend = Math.max(this.lastSend, line.t);\n } else if (line.ev === \"lookup\") {\n const pod = String(line[\"p\"]);\n const owner = line[\"o\"] === \"ok\" && typeof line[\"owner\"] === \"string\" ? line[\"owner\"] : null;\n if (line.t >= (this.owners.get(pod)?.t ?? 0)) this.owners.set(pod, { t: line.t, owner });\n }\n };\n servers(): PodLine[] {\n return [...this.pods.values()].filter((pod) => pod.labels[\"app\"] === SERVER).sort((a, b) => a.name.localeCompare(b.name));\n }\n}\n\n/** A context is the state of one cell in one environment. */\ninterface Context {\n readonly lab: KindLab;\n readonly cluster: Cluster;\n readonly cell: KindCell;\n readonly namespace: string;\n readonly tracker: Tracker;\n readonly clusterIp: string;\n /** This address is the VIP of the cell (`KindCell.vip`), or null for a cell with no VIP. */\n readonly vip: string | null;\n readonly loads: LoadSample[];\n readonly loader: StartedTestContainer;\n readonly gate: QuietGate;\n /** This map gives the baseline of each cell. It outlives an environment, because the highest one holds. */\n readonly baselines: Map<string, number>;\n /** This stamp of the wall clock is the end of the deploy of the cell. */\n readonly deployed: number;\n /** This flag tells that this environment took its baseline. */\n settled: boolean;\n readonly log: (line: string) => void;\n version: number;\n probed: string;\n /** This key is the server pods whose sidecars the LDS pollers read, or \"\" for none. */\n listened: string;\n /** This count is the replicas of the service at rest: one less while a scale-in holds. */\n expected: number;\n /** This map gives the PID of the app of each pod. A replica gets it from the last fault of the target `servers`, and a client from the last gap. */\n readonly pids: Map<string, number>;\n /** This is the speaker of MetalLB of the last SIGSTOP: its node and PID, until its SIGCONT. */\n speaker?: { readonly node: string; readonly pid: number; readonly pod: PodLine | null } | undefined;\n /** This broker is the owner of the topic at the start of the run, for a cell with a broker. It has its pod, its node, and the host that the lookups name. */\n broker: { readonly pod: string; readonly node: string; readonly host: string } | null;\n}\n\n/** This function tells why the cell is not at rest, or gives null when it is. */\nconst unsteady = (ctx: Context): string | null => {\n const { tracker, cell } = ctx;\n const servers = tracker.servers();\n if (servers.length !== ctx.expected) return `${servers.length} replicas, not ${ctx.expected}`;\n const busy = servers.find((pod) => pod.del || !pod.ready);\n if (busy !== undefined) return `${busy.name} is not ready`;\n if (new Set(servers.map((pod) => pod.labels[\"pod-template-hash\"])).size > 1) return \"two versions of the replicas\";\n if (hasRedis(cell)) {\n const redis = [...tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === REDIS);\n if (redis.length !== 1 || !redis[0]!.ready || redis[0]!.del) return \"Redis is not ready\";\n }\n const ips = servers.map((pod) => pod.ip ?? \"\").sort();\n const ready = [...tracker.slices.values()]\n .filter((slice) => slice[\"svc\"] === SERVER)\n .flatMap((slice) => (slice[\"eps\"] as Array<{ ip: string; r: boolean }>).filter((endpoint) => endpoint.r).map((endpoint) => endpoint.ip))\n .sort();\n if (ready.join() !== ips.join()) return `the EndpointSlice has ${ready.join(\" \")}, not ${ips.join(\" \")}`;\n const expected = ips.map((ip) => `${ip}:8080`).sort().join();\n for (const node of ctx.cluster.nodes)\n if ((tracker.backends.get(node) ?? []).join() !== expected) return `the rules of ${node} have ${(tracker.backends.get(node) ?? []).join(\" \")}`;\n // in a sidecar mesh, the client sidecar of each node picks the backend of each call from its endpoints\n if (cell.mesh?.mode === \"sidecar\")\n for (const node of ctx.cluster.nodes)\n if ((tracker.hosts.get(node) ?? []).join() !== expected) return `the client sidecar of ${node} has ${(tracker.hosts.get(node) ?? []).join(\" \") || \"no host\"}`;\n // the clients of a zone loss are only in the zone that stays. The clients of a VIP are outside, on one more \"node\"\n const nodes = new Set([...tracker.clients.values()].map((client) => client.node));\n const clientNodes = ctx.cluster.nodes.length - (cell.trigger.kind === \"zone-loss\" ? 1 : 0) + (cell.vip === undefined ? 0 : 1);\n if (nodes.size !== clientNodes) return `clients answer on ${nodes.size} nodes, not ${clientNodes}`;\n if (cell.vip !== undefined) {\n const outside = [...tracker.clients.values()].filter((client) => client.node === OUTSIDE).length;\n if (outside !== cell.vip.clients) return `${outside} clients outside answer, not ${cell.vip.clients}`;\n }\n for (const [pod, client] of tracker.clients) {\n if (tracker.last - client.ok > 1000) return `client ${pod} has no answer in the last second`;\n if (tracker.last - client.failed < 2000) return `client ${pod} failed in the last 2 s`;\n }\n return null;\n};\n\n/** This function waits until the cell is at rest, and gives null, or the reason when it is not at rest in time. */\nconst steady = async (ctx: Context, within: number): Promise<string | null> => {\n const end = Date.now() + within;\n let why = unsteady(ctx);\n while (why !== null && Date.now() < end) {\n await sleep(200);\n why = unsteady(ctx);\n }\n return why;\n};\n\n/** These taints of the node lifecycle controller mark a node that it does not see as ready. */\nconst NODE_LOSS_TAINTS: readonly string[] = [\"node.kubernetes.io/unreachable\", \"node.kubernetes.io/not-ready\"];\n\n/**\n * This function tells why a node is not at rest for the node lifecycle controller, or gives null. A\n * ready pod is not sufficient after a zone loss. The controller can still see the node as lost, and\n * then it does not mark the pods again at the next loss. The controller removes its taints only after\n * it sees the node ready.\n */\nconst nodeUnsteady = async (cluster: Cluster, name: string): Promise<string | null> => {\n const node = JSON.parse(await cluster.kubectl([\"get\", \"node\", name, \"-o\", \"json\"])) as {\n readonly spec?: { readonly taints?: ReadonlyArray<{ readonly key: string }> };\n readonly status?: { readonly conditions?: ReadonlyArray<{ readonly type: string; readonly status: string }> };\n };\n const ready = node.status?.conditions?.find((condition) => condition.type === \"Ready\")?.status;\n if (ready !== \"True\") return `the node ${name} has the Ready condition ${ready ?? \"none\"}`;\n const taints = (node.spec?.taints ?? []).map((taint) => taint.key).filter((key) => NODE_LOSS_TAINTS.includes(key));\n return taints.length === 0 ? null : `the node ${name} has the taints ${taints.join(\", \")}`;\n};\n\n/** This function waits until a node is at rest for the node lifecycle controller. It gives null, or the reason when the node is not at rest in time. */\nconst nodeSteady = async (cluster: Cluster, name: string, within: number): Promise<string | null> => {\n const end = Date.now() + within;\n let why = await nodeUnsteady(cluster, name);\n while (why !== null && Date.now() < end) {\n await sleep(1000);\n why = await nodeUnsteady(cluster, name);\n }\n return why;\n};\n\n/** This function samples the load of the VM for 4 s, and names the lab containers that are not of this run. */\nconst sample = async (ctx: Context, ours: ReadonlySet<string>): Promise<LoadSample> => {\n const result = await ctx.loader.exec([\"top\", \"-b\", \"-n\", \"2\", \"-d\", \"4\"]);\n const second = result.stdout.split(/^Mem:/m).at(-1) ?? \"\";\n const idle = Number(/(\\d+)% idle/.exec(second)?.[1] ?? \"0\") / 100;\n const top = second\n .split(\"\\n\")\n .filter((line) => /^\\s*\\d+\\s/.test(line))\n .slice(0, 6)\n .map((line) => line.trim().replace(/\\s+/g, \" \"));\n const listed = (await command(\"docker\", [\"ps\", \"--format\", \"{{.ID}} {{.Names}} {{.Image}}\"])).trim().split(\"\\n\");\n const foreign = listed\n .filter((line) => line !== \"\")\n .filter((line) => !ours.has(line.split(\" \")[1]!) && !ours.has(line.split(\" \")[0]!))\n .filter((line) => /loom-|kindest\\/node|toxiproxy|haproxy|redis|systems-kind-lab|f6-/.test(line.split(\" \")[2] ?? \"\"));\n return { t: Date.now(), idle, top, foreign };\n};\n\n/**\n * A run waits for a quiet VM for this time at the most, the bound of the wait for rest. Then the run\n * is void with its reason. In a run of SYS-42, a Cilium cluster at rest stayed at 47% to 59% idle.\n * This is below the share of 60%. That wait had no bound, and it took approximately 30 min.\n */\nconst QUIET_WITHIN = 300_000;\n\n/** This function gives the names of this lab at a sample: the prober and the watcher start again with new names. */\nconst oursAt = (ctx: Context, ours: ReadonlySet<string>): ReadonlySet<string> => new Set([...ours, ...ctx.lab.own(), ctx.loader.getName().replace(/^\\//, \"\")]);\n\n/**\n * This function takes the baseline of the cell in this environment, once. The first run after the\n * warm-up calls it. It waits until the cell has its age. The conntrack table fills for the life of\n * its entries, and the read of the table costs more until then.\n */\nconst takeBaseline = async (ctx: Context, ours: ReadonlySet<string>): Promise<void> => {\n if (ctx.settled) return;\n await sleep(Math.max(0, ctx.deployed + ctx.gate.settle - Date.now()));\n const samples: LoadSample[] = [];\n for (let index = 0; index < ctx.gate.samples; index += 1) samples.push(await sample(ctx, oursAt(ctx, ours)));\n ctx.loads.push(...samples);\n const baseline = baselineOf(ctx.baselines.get(ctx.cell.name) ?? null, samples);\n if (baseline !== null) ctx.baselines.set(ctx.cell.name, baseline);\n ctx.settled = true;\n ctx.log(` baseline: ${samples.map((each) => `${Math.round(each.idle * 100)}%`).join(\", \")} idle, the gate needs ${Math.round(neededIdle(ctx.gate, baseline) * 100)}%`);\n};\n\n/** This function starts the planned load of a cell for the longest wait of the gate. The load runs in the loader, out of the cluster. */\nconst startPlannedLoad = async (ctx: Context): Promise<void> => {\n const cores = ctx.cell.plannedLoad;\n if (cores === undefined) return;\n const spec = JSON.stringify({ cores, ms: QUIET_WITHIN + 20_000 });\n await ctx.loader.exec([\"sh\", \"-c\", `nohup node agent.mjs burn '${spec}' >/dev/null 2>&1 &`]);\n};\n\n/**\n * This function waits until the VM is quiet: no lab container of another agent, and an idle share of\n * the CPUs (`neededIdle`). It gives null, or the reason when the VM is not quiet in `within` ms.\n */\nconst quiet = async (ctx: Context, ours: ReadonlySet<string>, within = QUIET_WITHIN): Promise<string | null> => {\n const end = Date.now() + within;\n const needed = neededIdle(ctx.gate, ctx.baselines.get(ctx.cell.name) ?? null);\n for (;;) {\n const load = await sample(ctx, oursAt(ctx, ours));\n ctx.loads.push({ ...load, needed });\n if (load.foreign.length === 0 && load.idle >= needed) return null;\n const why = quietWhy(load.idle, needed, load.foreign);\n if (Date.now() >= end) return `the VM was not quiet in ${within / 1000} s: ${why}`;\n ctx.log(` waiting: ${why}`);\n await sleep(30_000);\n }\n};\n\n/**\n * This function deploys a cell in its namespace, with the watcher, the poll of the rules and the\n * clients. A client of gRPC starts after the server is ready: each failed connect of a channel makes\n * the backoff of grpc-js longer, up to 120 s.\n */\nconst deploy = async (lab: KindLab, cluster: Cluster, cell: KindCell, namespace: string, images: Images, vip: string | null): Promise<string> => {\n await cluster.apply([namespaceObject(cell, namespace)]);\n await lab.watch([namespace], { ztunnel: followsZtunnel(cell) });\n if (hasRedis(cell)) {\n await cluster.apply(redisObjects(namespace, images.redis));\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${REDIS}`, \"--timeout=180s\"]);\n }\n const grpc = stackOf(cell) === \"grpc\";\n if (grpc && images.grpc === undefined) throw new Error(`${cell.name}: the cluster has no image of grpc-go`);\n await cluster.apply(serverObjects(cell, namespace, grpc ? images.grpc! : lab.image, 1, cell.vip === undefined ? null : vip));\n const clusterIp = (await cluster.kubectl([\"-n\", namespace, \"get\", \"service\", SERVER, \"-o\", \"jsonpath={.spec.clusterIP}\"])).trim();\n // a client by name resolves the short name of the Service: the search list of its namespace gives it\n const clients = clientObjects(cell, namespace, lab.image, `${cell.workload.byName === true ? SERVER : clusterIp}:80`);\n if (!grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n if (grpc) await cluster.apply(clients);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `daemonset/${CLIENT}`, \"--timeout=300s\"]);\n // the producers of a broker, in the image of Pulsar\n if (cell.pulsar !== undefined) {\n if (images.pulsar === undefined) throw new Error(`${cell.name}: the cluster has no image of Pulsar`);\n await cluster.apply(pulsarObjects(cell, namespace, images.pulsar));\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${PRODUCER}`, \"--timeout=300s\"]);\n }\n // the clients of a VIP start outside the cluster after the announcer gave the Service its VIP\n if (cell.vip !== undefined) {\n if (vip === null) throw new Error(`${cell.name}: the cluster has no VIP`);\n const served = await vipServed(cluster, namespace, SERVER, vip);\n if (served !== null) throw new Error(`${cell.name}: ${served}`);\n await lab.outside({ target: `${vip}:80`, every: cell.workload.every, timeout: cell.workload.timeout, count: cell.vip.clients, arp: vip });\n }\n await lab.rules({ mode: cluster.scenario.dataplane, ns: namespace, svc: SERVER, ip: clusterIp, port: 80, every: RULES_EVERY });\n if (cell.mesh?.mode === \"sidecar\") await pollEndpoints(lab, cluster, namespace);\n return clusterIp;\n};\n\n/**\n * This function starts the EDS poll of the client sidecar on each node: the hosts of the server\n * cluster, each 50 ms. The agent enters the network namespace of the client pod through the PID of\n * its client process. The sidecar shares that namespace.\n */\nconst pollEndpoints = async (lab: KindLab, cluster: Cluster, namespace: string): Promise<void> => {\n const listed = JSON.parse(await cluster.kubectl([\"-n\", namespace, \"get\", \"pods\", \"-l\", `app=${CLIENT}`, \"-o\", \"json\"])) as {\n items: Array<{ metadata: { name: string; deletionTimestamp?: string }; spec: { nodeName?: string } }>;\n };\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const item of listed.items.filter((each) => each.metadata.deletionTimestamp === undefined)) {\n const node = item.spec.nodeName ?? \"\";\n pods.push({ node, pod: item.metadata.name, pid: await lab.pidOf(node, namespace, item.metadata.name, CLIENT) });\n }\n await lab.eds({ cluster: serverCluster(namespace), every: EDS_EVERY, pods });\n};\n\n/**\n * This function starts the LDS poll of the sidecar of each server pod, each 50 ms, for a cell that\n * polls them (`pollsListeners`). It starts the pollers again only when the server pods changed. The\n * agent enters the network namespace of the pod through the PID of its app.\n */\nconst pollListeners = async (ctx: Context): Promise<void> => {\n if (!pollsListeners(ctx.cell)) return;\n const servers = ctx.tracker.servers().filter((pod) => !pod.del && pod.node !== null);\n const key = servers.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.listened) return;\n const pods: Array<{ node: string; pod: string; pid: number }> = [];\n for (const pod of servers) pods.push({ node: pod.node!, pod: pod.name, pid: await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, \"app\") });\n await ctx.lab.lds({ every: LDS_EVERY, pods });\n ctx.listened = key;\n};\n\n/** This function starts the prober again when the pods of the cell changed: it watches the namespace of each pod. */\nconst probeThePods = async (ctx: Context): Promise<void> => {\n const pods = [...ctx.tracker.pods.values()].filter((pod) => !pod.del && pod.node !== null && pod.cs.every((status) => status.st === \"running\"));\n const key = pods.map((pod) => `${pod.name}/${pod.cs.map((status) => status.id).join(\"+\")}`).sort().join();\n if (key === ctx.probed) return;\n const netns: number[] = [];\n for (const pod of pods) {\n // a producer of a broker has one container of its name\n const container = pod.labels[\"app\"] === SERVER ? \"app\" : pod.labels[\"app\"] === CLIENT ? CLIENT : pod.labels[\"app\"] === PRODUCER ? PRODUCER : REDIS;\n const pid = await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, container);\n netns.push(await ctx.lab.netnsOf(pod.node!, pid));\n }\n await ctx.lab.probe([...new Set(netns)]);\n ctx.probed = key;\n};\n\n/**\n * This function gives the replicas of the server at this time, in the sequence of their names. The\n * first one is the target of a run. A client of gRPC holds its connection to one replica, and the\n * channels go to the replica that stays after a run. Thus for the stack `grpc`, the first replica\n * is the one that answers the most clients (`byClients`).\n */\nconst replicasOf = (ctx: Context): Replica[] => {\n const replicas = ctx.tracker.servers().map((pod) => ({ pod: pod.name, node: pod.node ?? \"\", ip: pod.ip ?? \"\", version: pod.labels[\"pod-template-hash\"] ?? \"\" }));\n return stackOf(ctx.cell) === \"grpc\" ? byClients(replicas, ctx.tracker.answers) : replicas;\n};\n\n/**\n * This time is how long before the commit of its rules the lab starts the injector in each replica.\n * It is more than the execs of one node before its commit: a read of its tables and the injector.\n */\nconst INJECT_LEAD_MS = 700;\n\n/**\n * This function puts the rules of an action in the network namespace of each replica, with each\n * client pod as a peer. The target `servers` gives all replicas, and `server` the target replica alone.\n * The rules of all replicas commit at one instant on\n * CLOCK_MONOTONIC (`--at`). Thus no client meets one replica with the rule and one without it. The\n * replicas of one node go one after the other, and the nodes at the same time.\n *\n * The fault finds the PID of each replica, and the clear uses it again: thus the heal comes soon\n * after its hold. Before the commit of a clear, the lab reads the filter table of each replica. Its\n * counters show if the rule matched the clients beside the rules of istio-cni. After the commit of a\n * fault, the lab reads the nat table, with the redirect of istio-cni. The stamp is the first commit\n * and the last end.\n */\nconst injectServers = async (ctx: Context, action: string, replicas: readonly Replica[]): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()]\n .filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.ip !== null)\n .map((pod) => pod.ip!)\n .sort();\n if (clients.length === 0 || replicas.length === 0) throw new Error(`the injector needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);\n const clear = action.startsWith(\"clear\");\n const byNode = new Map<string, number[]>();\n for (const replica of replicas) {\n const known = clear ? ctx.pids.get(replica.pod) : undefined;\n const pid = known ?? (await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, \"app\"));\n ctx.pids.set(replica.pod, pid);\n byNode.set(replica.node, [...(byNode.get(replica.node) ?? []), pid]);\n }\n const at = (await ctx.lab.now()) + INJECT_LEAD_MS;\n const args = [action, clients.join(\",\"), \"--at\", String(Math.round(at * 1e6))];\n const stamps = await Promise.all(\n [...byNode].map(async ([node, pids]) => {\n const out: Stamp[] = [];\n for (const pid of pids) {\n if (clear) await ctx.lab.tables(node, pid, \"filter\");\n out.push(await ctx.lab.inject(node, pid, args));\n }\n return out;\n }),\n );\n if (!clear) for (const [node, pids] of byNode) for (const pid of pids) await ctx.lab.tables(node, pid, \"nat\");\n const all = stamps.flat();\n return { t0: Math.min(...all.map((stamp) => stamp.t0)), t1: Math.max(...all.map((stamp) => stamp.t1)) };\n};\n\n/**\n * This function sends a signal to the process of each client pod: the target `clients`. The nodes go\n * at the same time. SIGUSR1 finds the PID of each client, and SIGUSR2 uses it again, thus the heal of\n * a gap comes soon after its hold. When that PID fails, the client started again, and the lab finds\n * its PID. The stamp is the first act and the last end.\n */\nconst signalClients = async (ctx: Context, signal: string): Promise<Stamp> => {\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0) throw new Error(`the signal ${signal} needs clients`);\n const stamps = await Promise.all(\n clients.map(async (pod) => {\n const node = pod.node!;\n const known = signal === \"SIGUSR2\" ? ctx.pids.get(pod.name) : undefined;\n if (known !== undefined) {\n try {\n return await ctx.lab.act(node, { op: \"signal\", pid: known, signal });\n } catch {\n // the client started again: its PID is new\n }\n }\n const pid = await ctx.lab.pidOf(node, ctx.namespace, pod.name, CLIENT);\n ctx.pids.set(pod.name, pid);\n return ctx.lab.act(node, { op: \"signal\", pid, signal });\n }),\n );\n return { t0: Math.min(...stamps.map((stamp) => stamp.t0)), t1: Math.max(...stamps.map((stamp) => stamp.t1)) };\n};\n\n/** This path in the root of a client container holds its placement: the backend of each of its slots, as JSON. */\nconst PLACE_FILE = \"/tmp/lab-place\";\n\n/**\n * This function writes the placement of the sessions of each client pod (`placementOf`). The target\n * is the first replica, and the other slots go to the other replicas. The next SIGHUP of the clients\n * reads it. The nodes go at the same time, and the stamp is the first act and the last end.\n */\nconst placeClients = async (ctx: Context, onTarget: number, replicas: readonly Replica[]): Promise<Stamp> => {\n const connections = ctx.cell.workload.connections;\n if (connections === undefined) throw new Error(\"a placement needs the sessions of each client (connections)\");\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0 || replicas.length === 0) throw new Error(`a placement needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);\n const placement = placementOf(clients.map((pod) => pod.name), connections, onTarget, replicas[0]!.pod, replicas.slice(1).map((replica) => replica.pod));\n const stamps = await Promise.all(\n clients.map(async (pod) => {\n const pid = await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, CLIENT);\n return ctx.lab.act(pod.node!, { op: \"write\", path: `/proc/${pid}/root${PLACE_FILE}`, data: JSON.stringify(placement.get(pod.name)) });\n }),\n );\n return { t0: Math.min(...stamps.map((stamp) => stamp.t0)), t1: Math.max(...stamps.map((stamp) => stamp.t1)) };\n};\n\n/**\n * This function gives the network namespace of each pod of a target, as a node and a PID in it. The\n * pods are each client pod (`clients`), or each replica (`servers`).\n */\nconst namespacesOf = async (ctx: Context, target: Target, replicas: readonly Replica[]): Promise<Array<{ node: string; pod: string; pid: number }>> => {\n if (target === \"servers\") return Promise.all(replicas.map(async (replica) => ({ node: replica.node, pod: replica.pod, pid: await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, \"app\") })));\n if (target !== \"clients\") throw new Error(`a sysctl or a middlebox takes the target clients or servers, not ${target}`);\n const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.node !== null);\n if (clients.length === 0) throw new Error(\"the step needs clients\");\n return Promise.all(clients.map(async (pod) => ({ node: pod.node!, pod: pod.name, pid: await ctx.lab.pidOf(pod.node!, ctx.namespace, pod.name, CLIENT) })));\n};\n\n/**\n * This function puts a stateful middlebox in the network namespace of each replica, or removes it.\n * Each client pod is a peer (`middleboxCommands`), and the nodes go at the same time. After the set,\n * the lab reads the filter table: it shows the chains of the middlebox. Before the removal, the lab\n * reads the filter table again: the counters show which segments the middlebox dropped.\n */\nconst middleboxServers = async (ctx: Context, idle: Quantity | null, port: number, replicas: readonly Replica[]): Promise<Stamp> => {\n const peers = [...ctx.tracker.pods.values()]\n .filter((pod) => pod.labels[\"app\"] === CLIENT && !pod.del && pod.ip !== null)\n .map((pod) => pod.ip!)\n .sort();\n // the removal comes from the table of each namespace, not from the peers at the time of the removal (middleboxRemoval)\n const commands = idle === null ? null : middleboxCommands(peers, idle, port);\n const t0 = await ctx.lab.now();\n const each = await namespacesOf(ctx, \"servers\", replicas);\n await Promise.all(\n each.map(async ({ node, pid }) => {\n if (commands !== null) {\n await ctx.lab.netns(node, pid, commands);\n await ctx.lab.tables(node, pid, \"filter\");\n return;\n }\n const removal = middleboxRemoval(await ctx.lab.tables(node, pid, \"filter\"));\n if (removal.length > 0) await ctx.lab.netns(node, pid, removal);\n const left = middleboxRemoval(await ctx.lab.tables(node, pid, \"filter\"));\n if (left.length > 0) throw new Error(`the middlebox on ${node} (PID ${pid}) has ${left.length} parts after its removal`);\n }),\n );\n return { t0, t1: await ctx.lab.now() };\n};\n\n/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */\nconst doStep = async (ctx: Context, step: Step, replicas: readonly Replica[], background: Array<Promise<unknown>>): Promise<Stamp | null> => {\n const { lab, cluster, namespace } = ctx;\n const target = replicas[0]!;\n const fill = (args: readonly string[]): string[] =>\n args.map((arg) =>\n arg.replaceAll(\"{namespace}\", namespace).replaceAll(POD, target.pod).replaceAll(NODE, target.node).replaceAll(VERSION, String(ctx.version)),\n );\n const where = async (which: Target): Promise<{ node: string; pid: number; pod: PodLine | null }> => {\n if (which === \"servers\") throw new Error(\"the target servers is a target of the injector only\");\n if (which === \"clients\") throw new Error(\"the target clients is a target of a signal only\");\n if (which === \"server\") return { node: target.node, pid: await lab.pidOf(target.node, namespace, target.pod, \"app\"), pod: null };\n // the speaker of MetalLB on the owner of the VIP: the node of the lost zone, as the hold before the run makes it\n if (which === \"speaker\") {\n const owner = lostNodeOf(cluster);\n const speakers = JSON.parse(\n await cluster.kubectl([\"-n\", METALLB_NAMESPACE, \"get\", \"pods\", \"-l\", \"app.kubernetes.io/component=speaker\", \"--field-selector\", `spec.nodeName=${owner}`, \"-o\", \"json\"]),\n ) as { items: Array<{ metadata: { name: string } }> };\n const pod = speakers.items[0];\n if (pod === undefined) throw new Error(`no speaker of MetalLB is on ${owner}`);\n return { node: owner, pid: await lab.pidOf(owner, METALLB_NAMESPACE, pod.metadata.name, \"speaker\"), pod: null };\n }\n if (which === \"broker\") {\n if (ctx.broker === null) throw new Error(`${ctx.cell.name}: the run has no broker that owns the topic`);\n return { node: ctx.broker.node, pid: await lab.pidOf(ctx.broker.node, PULSAR_NAMESPACE, ctx.broker.pod, \"pulsar-broker\"), pod: null };\n }\n if (which === \"redis\") {\n const redis = [...ctx.tracker.pods.values()].find((pod) => pod.labels[\"app\"] === REDIS && !pod.del)!;\n return { node: redis.node!, pid: await lab.pidOf(redis.node!, namespace, redis.name, REDIS), pod: redis };\n }\n // the target kube-proxy: the kube-proxy of the first worker. The dataplane cilium has no kube-proxy.\n if (cluster.scenario.kubeProxy === null) throw new Error(`${ctx.cell.name}: the target kube-proxy needs kube-proxy, and the dataplane ${cluster.scenario.dataplane} has none`);\n const worker = cluster.nodes[1] ?? cluster.nodes[0]!;\n const proxy = JSON.parse(\n await cluster.kubectl([\"-n\", \"kube-system\", \"get\", \"pods\", \"-l\", \"k8s-app=kube-proxy\", \"--field-selector\", `spec.nodeName=${worker}`, \"-o\", \"json\"]),\n ) as { items: Array<{ metadata: { name: string } }> };\n return { node: worker, pid: await lab.pidOf(worker, \"kube-system\", proxy.items[0]!.metadata.name, \"kube-proxy\"), pod: null };\n };\n switch (step.do) {\n case \"kubectl\": {\n const t0 = await lab.now();\n if (step.args.includes(`LAB_VERSION=${VERSION}`)) ctx.version += 1;\n const call = cluster.kubectl(fill(step.args));\n if (step.background === true) {\n background.push(call.catch((error: unknown) => ctx.log(` kubectl ${step.args.join(\" \")}: ${String(error)}`)));\n return { t0, t1: t0 };\n }\n await call;\n return { t0, t1: await lab.now() };\n }\n case \"signal\": {\n if (step.target === \"clients\") return signalClients(ctx, step.signal);\n // the SIGCONT of a speaker uses the PID of its SIGSTOP: no lookup delays the end of the pause\n const cached = step.target === \"speaker\" && step.signal === \"SIGCONT\" ? ctx.speaker : undefined;\n const at = cached ?? (await where(step.target));\n if (step.target === \"speaker\") ctx.speaker = step.signal === \"SIGSTOP\" ? at : undefined;\n return lab.act(at.node, { op: \"signal\", pid: at.pid, signal: step.signal });\n }\n case \"pause\": {\n const at = await where(step.target);\n const stamp = lab.act(at.node, { op: \"pause\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n background.push(stamp);\n const now = await lab.now();\n return { t0: now, t1: now };\n }\n case \"inject\": {\n if (step.target === \"servers\") return injectServers(ctx, step.action, replicas);\n // the partition of the target: its namespace only, with each client pod as a peer\n if (step.target === \"server\") return injectServers(ctx, step.action, [target]);\n const at = await where(step.target);\n let first: Stamp | null = null;\n for (const replica of replicas) {\n const stamp = await lab.inject(at.node, at.pid, [step.action, replica.ip]);\n first ??= stamp;\n }\n return first;\n }\n case \"flag\": {\n const at = await where(step.target);\n return step.on\n ? lab.act(at.node, { op: \"write\", path: `/proc/${at.pid}/root/tmp/lab-fail`, data: \"1\" })\n : lab.act(at.node, { op: \"remove\", path: `/proc/${at.pid}/root/tmp/lab-fail` });\n }\n case \"cpu\": {\n const at = await where(step.target);\n const cgroup = await lab.cgroupOf(at.node, at.pid);\n return lab.act(at.node, { op: \"write\", path: `${cgroup}/cpu.max`, data: step.max });\n }\n case \"stall\": {\n const at = await where(step.target);\n return lab.act(at.node, { op: \"stall\", pid: at.pid, ms: valueIn(step.time, \"ms\") });\n }\n case \"wait\":\n await sleep(valueIn(step.time, \"ms\"));\n return null;\n case \"apply\": {\n // the marks are in the strings of the object: its JSON keeps them as they are\n const object = JSON.parse(fill([JSON.stringify(step.object)])[0]!) as Record<string, unknown>;\n const t0 = await lab.now();\n await cluster.apply([object]);\n return { t0, t1: await lab.now() };\n }\n case \"sysctl\": {\n const command = sysctlCommand(step.key, step.value);\n const t0 = await lab.now();\n await Promise.all((await namespacesOf(ctx, step.target, replicas)).map(({ node, pid }) => lab.netns(node, pid, [command])));\n return { t0, t1: await lab.now() };\n }\n case \"dns\": {\n if (step.target !== \"clients\") throw new Error(`a drop of DNS takes the target clients, not ${step.target}`);\n // the namespaces first, then the rules of all nodes at the same time: the stamp is near the rules\n const each = await namespacesOf(ctx, step.target, replicas);\n const t0 = await lab.now();\n await Promise.all(each.map(({ node, pid }) => lab.netns(node, pid, dnsCommands(step.on))));\n return { t0, t1: await lab.now() };\n }\n case \"certificates\": {\n // a failed read is evidence too: the line has its error, and the run goes on\n const t0 = await lab.now();\n const certificates = await ztunnelCertificates(cluster).catch((error: unknown) => String(error).slice(0, 400));\n const t1 = await lab.now();\n lab.record(typeof certificates === \"string\" ? { ev: \"certificates\", t: t0, t1, error: certificates } : { ev: \"certificates\", t: t0, t1, certificates });\n return { t0, t1 };\n }\n case \"middlebox\":\n if (step.target !== \"servers\") throw new Error(`a middlebox takes the target servers, not ${step.target}`);\n return middleboxServers(ctx, step.idle, step.port, replicas);\n case \"zone\":\n // the agent of the lost node is in its own container, thus the freeze of the node does not stop it\n return lab.act(lostNodeOf(cluster), { op: \"zone\", lost: step.lost });\n case \"place\":\n return placeClients(ctx, step.onTarget, replicas);\n case \"netem\": {\n if (step.target !== \"redis\") throw new Error(`a netem takes the target redis, not ${step.target}`);\n // one qdisc on the link of the Redis pod delays each reply to each replica\n const at = await where(step.target);\n const spec = step.delay === null ? [\"clear-netem\", POD_LINK] : [\"netem\", POD_LINK, \"delay\", `${Math.round(valueIn(step.delay, \"ms\"))}ms`];\n return lab.inject(at.node, at.pid, spec);\n }\n }\n};\n\n/** This function gives the node of the lost zone: the last worker (`zoneOf`). */\nconst lostNodeOf = (cluster: Cluster): string => cluster.nodes.at(-1)!;\n\n/** This function gives the workers of a cluster: each node but the control plane, the first (`nodeNames`). */\nconst workersOf = (cluster: Cluster): string[] => cluster.nodes.slice(1);\n\n/**\n * This function adds a line `vip` to the lines. The line has the state of the announcer, the MAC\n * address of each node on the Docker network, and the name of the announcer. The state of kube-vip\n * is its Lease. The state of MetalLB is the nodes that announce the Service. It also has the wall clock of the runner with its CLOCK_MONOTONIC. The times of the\n * Lease and of the logs are on the wall clock, and each stamp of the lab is on CLOCK_MONOTONIC. The containers of\n * the runner share one kernel, thus the pair converts one into the other.\n */\nconst recordVip = async (ctx: Context): Promise<void> => {\n const wall = Date.now();\n const t = Number(process.hrtime.bigint()) / 1e6;\n const announcer = announcerOf(ctx.cell);\n const state = await announcer.record(ctx.cluster, ctx.namespace, SERVER);\n const { macs } = await vipNetworkOf(ctx.cluster.network);\n ctx.lab.record({ ev: \"vip\", t, wall, vip: ctx.vip, announcer: announcer.name, ...state, macs });\n};\n\n/** This function records the brokers after a run, with the kept lines of each broker. It stamps the wall clock and the monotonic clock. */\nconst recordPulsar = async (ctx: Context): Promise<void> => {\n const wall = Date.now();\n const t = Number(process.hrtime.bigint()) / 1e6;\n ctx.lab.record({ ev: \"pulsar-record\", t, wall, version: PULSAR_VERSION, owner: ctx.broker?.pod ?? null, ...(await pulsarRecord(ctx.cluster)) });\n};\n\n/** This function puts the replicas of a node first, and keeps the sequence of the others. */\nconst lostFirst = (replicas: readonly Replica[], node: string): Replica[] => [\n ...replicas.filter((replica) => replica.node === node),\n ...replicas.filter((replica) => replica.node !== node),\n];\n\n/** This function tells if a run is over: the trigger is done, and the cell is at rest again. */\nconst over = (\n ctx: Context,\n run: { readonly target: Replica | null; readonly replicas: readonly Replica[]; readonly restarts: ReadonlyMap<string, number> },\n lines: readonly Line[],\n): boolean => {\n if (unsteady(ctx) !== null) return false;\n const kind = ctx.cell.trigger.kind;\n if ((kind === \"crash\" || kind === \"liveness-restart\") && run.target !== null) {\n const pod = ctx.tracker.pods.get(run.target.pod);\n const now = pod === undefined ? 0 : pod.cs.reduce((sum, status) => sum + status.rc, 0);\n return now > (run.restarts.get(run.target.pod) ?? 0);\n }\n if (run.target !== null && [\"pod-deletion\", \"node-drain\", \"scale-in\", \"freeze-process\"].includes(kind) && (ctx.cell.template === \"T07\" || ctx.cell.template === \"T15\"))\n return kind === \"scale-in\" || kind === \"node-drain\"\n ? run.replicas.some((replica) => !ctx.tracker.pods.has(replica.pod))\n : !ctx.tracker.pods.has(run.target.pod);\n if (kind === \"rolling-update\" || kind === \"rollout\") return run.replicas.every((replica) => !ctx.tracker.pods.has(replica.pod));\n if (kind === \"app-stall\") return lines.some((line) => line.ev === \"stall\" && line[\"phase\"] === \"end\");\n if (kind === \"ztunnel-restart\") return ztunnelReplaced(lines, ctx.cluster.nodes);\n return true;\n};\n\n/** This function gives the replica that the trigger stopped. For a scale-in or a drain, the controller chooses it. */\nconst stoppedOf = (cell: KindCell, replicas: readonly Replica[], target: Replica | null, lines: readonly Line[]): Replica | null =>\n deletesAPod(cell) ? (replicas.find((replica) => lines.some((line) => line.ev === \"pod\" && line[\"name\"] === replica.pod && line[\"del\"] === true)) ?? target) : target;\n\n/**\n * This function does one run of a cell: at rest, the trigger, the heal, then the observation. The\n * observation lasts until the cell is at rest again, and until each predicted window can close\n * (`observationEnd`).\n */\nconst runOnce = async (ctx: Context, env: number, index: number, warmup: boolean, ours: Set<string>): Promise<KindRun> => {\n const { lab, cell, cluster, namespace } = ctx;\n const plan = planOf(cell);\n const empty = (why: string, start: number): KindRun => ({\n env, index, warmup, start, end: start, acts: { fault: null, heal: null, resume: null }, target: null, paused: null, replicas: [], void: why, lines: lab.take(), dropped: {},\n });\n const atRest = await steady(ctx, 300_000);\n if (atRest !== null) return empty(`the cell was not at rest: ${atRest}`, await lab.now());\n await probeThePods(ctx);\n await pollListeners(ctx);\n if (!warmup) {\n await takeBaseline(ctx, ours);\n await startPlannedLoad(ctx);\n }\n const loud = await quiet(ctx, ours);\n if (loud !== null) {\n ctx.log(` ${loud}`);\n return empty(loud, await lab.now());\n }\n const zoneLoss = cell.trigger.kind === \"zone-loss\";\n // the node of a zone loss must be ready for the node lifecycle controller too, with no taint of the last loss\n const lost = zoneLoss ? await nodeSteady(cluster, lostNodeOf(cluster), 300_000) : null;\n if (lost !== null) return empty(`the node of the lost zone was not at rest: ${lost}`, await lab.now());\n // the holder of a VIP is the node of the lost zone at the start of each run, with its announcer\n // ready on each worker. Then the run waits until each client outside sends to the MAC address of the\n // holder: a gratuitous ARP of the holder reached it. The last wait is the settle of the announcer and\n // the timeout. Thus the gratuitous ARPs of the move end before the fault, and each request of the move ends\n if (cell.vip !== undefined) {\n const announcer = announcerOf(cell);\n const holder = lostNodeOf(cluster);\n const held = await announcer.hold(cluster, holder, workersOf(cluster), ctx.namespace, SERVER);\n if (held !== null) return empty(`the VIP is not on ${holder}: ${held}`, await lab.now());\n if (ctx.vip === null) return empty(`${cell.name}: the cluster has no VIP`, await lab.now());\n const mac = (await vipNetworkOf(cluster.network)).macs[holder];\n if (mac === undefined) return empty(`the node ${holder} has no MAC address on the Docker network`, await lab.now());\n const end = Date.now() + valueIn(announcer.settle, \"ms\") + 10_000;\n let moved = macsAt(await lab.outsideMacs(ctx.vip), mac, cell.vip.clients);\n while (moved !== null && Date.now() < end) {\n await sleep(250);\n moved = macsAt(await lab.outsideMacs(ctx.vip), mac, cell.vip.clients);\n }\n if (moved !== null) return empty(`the clients outside do not send to ${holder} (${mac}): ${moved}`, await lab.now());\n await sleep(valueIn(announcer.settle, \"ms\") + valueIn(cell.workload.timeout, \"ms\") + 1000);\n }\n if ((await steady(ctx, 300_000)) !== null) return empty(\"the cell was not at rest after the wait for a quiet VM\", await lab.now());\n // the broker that owns the topic: each broker is ready, each producer sends, and each lookup names that broker\n ctx.broker = null;\n if (cell.pulsar !== undefined) {\n const tracker = ctx.tracker;\n const held = await holdPulsar(cluster, () => ({ owners: tracker.owners, sends: tracker.sends, last: tracker.lastSend }), cell.pulsar.clients);\n if (typeof held === \"string\") return empty(`the producers were not at rest: ${held}`, await lab.now());\n ctx.broker = { pod: held.broker.pod, node: held.broker.node, host: held.host };\n }\n // the target of a zone loss is the replica of the lost node: the zone spread puts one there. With\n // a VIP, no replica is there: the loss takes away the holder of the VIP, and the run has no target\n const replicas = zoneLoss ? lostFirst(replicasOf(ctx), lostNodeOf(cluster)) : replicasOf(ctx);\n if (zoneLoss && cell.vip === undefined && replicas[0]?.node !== lostNodeOf(cluster)) return empty(`no replica is on the node of the lost zone, ${lostNodeOf(cluster)}`, await lab.now());\n const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));\n const target =\n cell.vip !== undefined || cell.pulsar !== undefined || (hasRedis(cell) && [\"rst\", \"blackhole\", \"freeze-process\", \"peer-gone-without-close\", \"rollout\", \"latency\"].includes(cell.trigger.kind)) ? null : replicas[0]!;\n // the paused node is the node of kube-proxy that a freeze stops, or the node of the lost zone\n const paused = cell.template === \"T07\" && cell.trigger.kind === \"freeze-process\" && cell.vip === undefined ? (cluster.nodes[1] ?? cluster.nodes[0]!) : zoneLoss ? lostNodeOf(cluster) : null;\n // A SYN with no answer before the run keeps its NAT for 120 s (SYN_SENT). A client that takes its\n // port again in the run meets it, and the restore of the last run leaves such SYNs. Each run starts with none\n const cleared = await lab.forgetHandshakes(ctx.clusterIp, 80);\n lab.take();\n for (const key of Object.keys(lab.dropped)) delete lab.dropped[key];\n const start = await lab.now();\n const background: Array<Promise<unknown>> = [];\n let fault: number | null = null;\n let resume: number | null = null;\n if (cell.trigger.kind === \"scale-in\") ctx.expected = cell.pod.replicas - 1;\n const notes: string[] = [];\n const forgotten = Object.entries(cleared).filter(([, count]) => count > 0);\n if (forgotten.length > 0) notes.push(`before the run, the lab deleted SYN_SENT entries of the ClusterIP: ${forgotten.map(([node, count]) => `${count} on ${node}`).join(\", \")}`);\n // the steps before the fault. A failed one makes the run void with its reason, after the restore\n for (const step of plan.prepare ?? []) {\n try {\n await doStep(ctx, step, replicas, background);\n } catch (error) {\n const why = `the step ${step.do} before the fault failed: ${String(error).slice(0, 400)}`;\n ctx.log(` ${why}`);\n for (const each of plan.restore) await doStep(ctx, each, replicas, background).catch((failure: unknown) => ctx.log(` the restore ${each.do} failed: ${String(failure)}`));\n return empty(why, start);\n }\n }\n for (const step of plan.inject) {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && fault === null) fault = stamp.t0;\n }\n let heal: number | null = null;\n // the heal of one fault, `hold` after its stamp: the stamp of its first act, or null\n const healAfter = async (from: number | null, hold: Quantity): Promise<number | null> => {\n const now = await lab.now();\n await sleep(Math.max(0, valueIn(hold, \"ms\") - (now - (from ?? now))));\n let stamped: number | null = null;\n for (const step of plan.heal) {\n try {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && stamped === null) stamped = stamp.t0;\n } catch (error) {\n notes.push(`the heal ${step.do} failed: ${String(error)}`);\n }\n }\n return stamped;\n };\n const cycles: Cycle[] = [];\n if (plan.hold !== null) {\n heal = await healAfter(fault, plan.hold);\n if (fault !== null && heal !== null) cycles.push({ fault, heal });\n // each flap: the rest from the heal before it, the inject again, and its heal after its own hold\n for (const flap of plan.flaps ?? []) {\n const now = await lab.now();\n await sleep(Math.max(0, valueIn(flap.rest, \"ms\") - (now - (heal ?? now))));\n let again: number | null = null;\n for (const step of plan.inject) {\n try {\n const stamp = await doStep(ctx, step, replicas, background);\n if (stamp !== null && again === null) again = stamp.t0;\n } catch (error) {\n notes.push(`the inject ${step.do} of a flap failed: ${String(error)}`);\n }\n }\n const healed = await healAfter(again, flap.hold);\n if (again !== null && healed !== null) cycles.push({ fault: again, heal: healed });\n heal = healed ?? heal;\n }\n }\n const flapped = (plan.flaps ?? []).length > 0;\n if (flapped && cycles.length !== (plan.flaps ?? []).length + 1) notes.push(`the run has ${cycles.length} cycles with stamps of the ${(plan.flaps ?? []).length + 1} of the trigger`);\n const flapping = flapped ? { cycles } : {};\n const settled = await Promise.allSettled(background);\n for (const each of settled) {\n if (each.status === \"fulfilled\" && typeof each.value === \"object\" && each.value !== null && \"t1\" in each.value) resume = (each.value as Stamp).t1;\n }\n // the observation: at least 3 s after the last act, until the trigger happened and the cell is at rest again\n const limit = Date.now() + 240_000 + valueIn(cell.pod.grace, \"ms\") + valueIn(cell.pod.preStop, \"ms\");\n await sleep(3000);\n while (Date.now() < limit && !over(ctx, { target, replicas, restarts }, lab.lines)) await sleep(250);\n // then until each predicted window can close and each request that started in it ended (`observationEnd`)\n const until = observationEnd(cell, { start, acts: { fault, heal, resume, ...flapping }, target: stoppedOf(cell, replicas, target, lab.lines), replicas, lines: lab.lines });\n if (until !== null) {\n const wait = Math.min(until - (await lab.now()), limit - Date.now());\n if (wait > 0) await sleep(wait);\n }\n const end = await lab.now();\n const timedOut = Date.now() >= limit;\n // the Lease of a VIP after the run gives the time that the new holder took it\n if (cell.vip !== undefined) await recordVip(ctx).catch((error: unknown) => notes.push(`the record of the VIP failed: ${String(error)}`));\n if (cell.pulsar !== undefined) await recordPulsar(ctx).catch((error: unknown) => notes.push(`the record of Pulsar failed: ${String(error)}`));\n for (const step of plan.restore) {\n try {\n await doStep(ctx, step, replicas, background);\n } catch (error) {\n notes.push(`the restore ${step.do} failed: ${String(error)}`);\n }\n }\n ctx.expected = cell.pod.replicas;\n for (const note of notes) ctx.log(` ${note}`);\n const lines = lab.take();\n const stopped = stoppedOf(cell, replicas, target, lines);\n const restarted = lines.some((line) => line.ev === \"pod\" && (line[\"cs\"] as Array<{ rc: number }>).some((status) => status.rc > 0));\n if (restarted) {\n // a container that restarted gets a back-off on its next restart: new pods start without one\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"restart\", `deployment/${SERVER}`]);\n await cluster.kubectl([\"-n\", namespace, \"rollout\", \"status\", `deployment/${SERVER}`, \"--timeout=300s\"]);\n }\n if (cell.trigger.kind === \"node-drain\") await rebalance(ctx);\n ctx.log(` ${warmup ? \"warm-up\" : `run ${index}`}: ${sec(end - start)} s${timedOut ? \", not at rest in time\" : \"\"}`);\n return {\n env, index, warmup, start, end, acts: { fault, heal, resume, ...flapping }, target: stopped, paused, ...(ctx.broker === null ? {} : { broker: ctx.broker.host }), replicas, void: null, lines, dropped: { ...lab.dropped }, notes,\n };\n};\n\nconst sec = (ms: number): number => Math.round(ms) / 1000;\n\n/** This function moves a replica back to a node without one after a drain: it deletes one replica of the node with two. */\nconst rebalance = async (ctx: Context): Promise<void> => {\n await steady(ctx, 300_000);\n const servers = ctx.tracker.servers();\n const byNode = new Map<string, string[]>();\n for (const pod of servers) byNode.set(pod.node ?? \"\", [...(byNode.get(pod.node ?? \"\") ?? []), pod.name]);\n const crowded = [...byNode.values()].find((pods) => pods.length > 1);\n if (crowded !== undefined && byNode.size < ctx.cluster.nodes.length - 1)\n await ctx.cluster.kubectl([\"-n\", ctx.namespace, \"delete\", \"pod\", crowded[0]!, \"--wait=true\"]);\n};\n\n/** This function writes the result of a cell: its raw record, and one line of its verdict in `summary.ndjson`. */\nexport const writeResult = (out: string, result: KindCellResult): void => {\n mkdirSync(out, { recursive: true });\n const record: KindCellRecord = { format: FORMAT, ...result };\n writeFileSync(join(out, `${slug(result.cell.name)}.json`), JSON.stringify(record));\n const { verdict, cell } = result;\n writeFileSync(\n join(out, \"summary.ndjson\"),\n `${JSON.stringify({\n id: cell.id,\n name: cell.name,\n mode: cell.mode,\n trigger: cell.trigger.kind,\n prediction: cell.prediction.kind,\n decision: verdict.decision,\n counted: verdict.counted,\n voided: verdict.voided,\n failed: verdict.failed,\n median: verdict.median,\n why: verdict.why,\n substrate: substrateKey(substrateOf(result.manifests[0] ?? {}, result.at, \"E2\")),\n })}\\n`,\n { flag: \"a\" },\n );\n};\n\n/**\n * This function does the runs of a catalog of kind cells. The cells of one kube-proxy mode and node\n * count share a cluster. Each group gets new clusters in sequence, one for each environment. Each\n * environment does its share of the runs of each cell, after one warm-up. The function deletes each\n * cluster after its cells.\n */\nexport const runKindCells = async (cells: readonly KindCell[], options: RunOptions): Promise<KindCellResult[]> => {\n const environments = options.environments ?? 2;\n const image = await labImage();\n const redisImage = await localPin(REDIS_IMAGE);\n // the server of the stack grpc: the image of grpc-go of the harness, built from its tag\n const grpcImage = cells.some((cell) => stackOf(cell) === \"grpc\") ? await imageOf({ kind: \"shipped\", name: \"grpc-go\" }) : undefined;\n // Istio: each image of each flavor of the cells pulled once by its digest, and tagged with it, for `kind load`\n const flavors = new Set(cells.flatMap((cell) => (cell.mesh === undefined ? [] : [cell.mesh.flavor])));\n const pinned = new Map<keyof typeof ISTIO_IMAGES, string>();\n for (const name of new Set([...flavors].flatMap((flavor) => MESH_IMAGES[flavor]))) pinned.set(name, await localPin(ISTIO_IMAGES[name]));\n const meshImages: MeshImages | undefined =\n flavors.size === 0 ? undefined : { pilot: pinned.get(\"pilot\")!, proxy: pinned.get(\"proxy\")!, cni: pinned.get(\"cni\"), ztunnel: pinned.get(\"ztunnel\") };\n // Cilium: each image pulled once by its digest, if a cell has the dataplane cilium\n const cilium: CiliumImages | undefined = cells.some((cell) => cell.mode === \"cilium\")\n ? { agent: await localPin(CILIUM_IMAGES.agent), operator: await localPin(CILIUM_IMAGES.operator), envoy: await localPin(CILIUM_IMAGES.envoy) }\n : undefined;\n // the announcers of the VIPs: each image pulled once by its digest, if a cell has a VIP with that announcer\n const announcerImages = new Map<string, Record<string, string>>();\n for (const announcer of [KUBE_VIP_ANNOUNCER, METALLB_ANNOUNCER]) {\n if (!cells.some((cell) => cell.vip !== undefined && announcerOf(cell).name === announcer.name)) continue;\n const pinned: Record<string, string> = {};\n for (const [name, ref] of Object.entries(announcer.images)) pinned[name] = await localPin(ref);\n announcerImages.set(announcer.name, pinned);\n }\n // Pulsar: its image pulled once by its digest, if a cell has a broker\n const pulsarImage = cells.some((cell) => cell.pulsar !== undefined) ? await localPin(PULSAR_IMAGE) : undefined;\n const images: Images = { lab: image, redis: redisImage, grpc: grpcImage, mesh: meshImages, pulsar: pulsarImage };\n // the cells of one kube-proxy mode, node count and mesh flavor share a cluster\n const groups = new Map<string, KindCell[]>();\n for (const cell of cells) groups.set(clusterKey(cell), [...(groups.get(clusterKey(cell)) ?? []), cell]);\n const results: KindCellResult[] = [];\n const baselines = new Map<string, number>();\n for (const group of groups.values()) {\n const partial = new Map<string, { runs: KindRun[]; manifests: EnvironmentManifest[]; loads: LoadSample[] }>();\n for (let env = 0; env < environments; env += 1) {\n const s = scenario(clusterNameOf(group[0]!, env), group[0]!.mode, group[0]!.workers, undefined, zoned(group[0]!));\n const dir = join(options.work ?? join(tmpdir(), \"systems-kind-work\"), s.name);\n const mesh = group[0]!.mesh;\n options.log(`cluster ${s.name}: ${s.dataplane}, ${s.workers} workers${mesh === undefined ? \"\" : `, Istio ${mesh.flavor}`}`);\n const cluster = await createCluster(s, dir);\n let lab: KindLab | null = null;\n let loader: StartedTestContainer | null = null;\n try {\n // the CNI first: with no CNI, no pod of the lab can start\n if (s.cni === \"cilium\") {\n if (cilium === undefined) throw new Error(`${group[0]!.name}: the run has no images of Cilium`);\n for (const each of [cilium.agent, cilium.operator, cilium.envoy]) await cluster.load(each);\n const began = Date.now();\n await installCilium(cluster, cilium);\n options.log(` Cilium ${CILIUM_VERSION} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n await cluster.load(image);\n await cluster.load(redisImage);\n if (grpcImage !== undefined && group.some((cell) => stackOf(cell) === \"grpc\")) await cluster.load(grpcImage);\n if (mesh !== undefined) {\n if (images.mesh === undefined) throw new Error(`${group[0]!.name}: the run has no images of Istio`);\n for (const name of MESH_IMAGES[mesh.flavor]) {\n const each = images.mesh[name];\n if (each === undefined) throw new Error(`${group[0]!.name}: the run has no image ${name} of Istio`);\n await cluster.load(each);\n }\n const began = Date.now();\n await installMesh(cluster, mesh.flavor, images.mesh);\n options.log(` Istio ${mesh.flavor} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n // the announcer on each worker, and the VIP on the node of the lost zone: its address near the end of the Docker network\n let vip: string | null = null;\n if (group[0]!.vip !== undefined) {\n const announcer = announcerOf(group[0]!);\n const pinned = announcerImages.get(announcer.name);\n if (pinned === undefined) throw new Error(`${group[0]!.name}: the run has no images of ${announcer.name}`);\n for (const each of Object.values(pinned)) await cluster.load(each);\n const began = Date.now();\n vip = announcer.vipOf((await vipNetworkOf(cluster.network)).subnet, lostNodeOf(cluster), workersOf(cluster));\n await announcer.install(cluster, pinned, lostNodeOf(cluster), workersOf(cluster), vip, group[0]!.vip);\n options.log(` ${announcer.name} installed in ${Math.round((Date.now() - began) / 100) / 10} s, the VIP ${vip}`);\n }\n // Pulsar: one ZooKeeper, one bookie and two brokers, for the cells with a broker\n if (group[0]!.pulsar !== undefined) {\n if (pulsarImage === undefined) throw new Error(`${group[0]!.name}: the run has no image of Pulsar`);\n await cluster.load(pulsarImage);\n const began = Date.now();\n await installPulsar(cluster, pulsarImage);\n options.log(` Pulsar ${PULSAR_VERSION} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);\n }\n lab = await openKindLab(cluster, image);\n const { GenericContainer } = await import(\"testcontainers\");\n const Shared = class extends GenericContainer {\n withPidMode(mode: string): this {\n (this as unknown as { hostConfig: { PidMode?: string } }).hostConfig.PidMode = mode;\n return this;\n }\n };\n loader = await new Shared(image).withPidMode(\"host\").withNetworkMode(\"none\").withCommand([\"sleep\", \"infinity\"]).start();\n const manifest = await cluster.manifest();\n const ours = new Set<string>([...cluster.nodes]);\n for (const cell of group) {\n const namespace = namespaceOf(cell);\n options.log(`${cell.name} (${cell.id}), environment ${env}`);\n const tracker = new Tracker();\n const off = lab.listen(tracker.see);\n const loads: LoadSample[] = [];\n // the environment of ztunnel for the cell, before its pods: thus its rollout ends no flow of the cell\n const ztunnelEnv = cell.mesh?.ztunnel;\n try {\n if (ztunnelEnv !== undefined) await setZtunnelEnv(cluster, ztunnelEnv, false);\n const clusterIp = await deploy(lab, cluster, cell, namespace, images, vip);\n for (const name of [...lab.own(), loader.getName().replace(/^\\//, \"\")]) ours.add(name);\n const ctx: Context = { lab, cluster, cell, namespace, tracker, clusterIp, vip: cell.vip === undefined ? null : vip, loads, loader, gate: { ...QUIET_GATE, flat: options.idle ?? QUIET_GATE.flat }, baselines, deployed: Date.now(), settled: false, log: options.log, version: 1, probed: \"\", listened: \"\", expected: cell.pod.replicas, pids: new Map(), broker: null };\n const share = Math.ceil(cell.runs / environments) - (env < cell.runs % environments || cell.runs % environments === 0 ? 0 : 1);\n const runs: KindRun[] = [await runOnce(ctx, env, -1, true, ours)];\n for (let index = 0; index < share; index += 1) runs.push(await runOnce(ctx, env, index, false, ours));\n const entry = partial.get(cell.name) ?? { runs: [], manifests: [], loads: [] };\n entry.runs.push(...runs);\n entry.manifests.push({\n env,\n scenario: s,\n cluster: manifest,\n substrate: substrateOn(\"E2\"),\n images: {\n lab: image,\n redis: redisImage,\n ...(stackOf(cell) === \"grpc\" ? { grpc: grpcImage! } : {}),\n ...(cell.mesh === undefined ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]]))),\n ...(s.cni === \"cilium\" ? Object.fromEntries(Object.entries(CILIUM_IMAGES).map(([name, ref]) => [`cilium-${name}`, ref])) : {}),\n ...(cell.vip === undefined ? {} : announcerOf(cell).images),\n ...(cell.pulsar === undefined ? {} : { pulsar: PULSAR_IMAGE }),\n },\n });\n entry.loads.push(...loads);\n partial.set(cell.name, entry);\n mkdirSync(join(options.out, \"partial\"), { recursive: true });\n writeFileSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), JSON.stringify({ cell, runs, manifest, loads }));\n } finally {\n off();\n await lab.outside(null).catch(() => undefined);\n await lab.rules(null).catch(() => undefined);\n await lab.eds(null).catch(() => undefined);\n await lab.lds(null).catch(() => undefined);\n await lab.stopProbe();\n await cluster.kubectl([\"delete\", \"namespace\", namespace, \"--wait=true\", \"--timeout=300s\"]).catch((error: unknown) => options.log(` ${String(error)}`));\n if (ztunnelEnv !== undefined) await setZtunnelEnv(cluster, ztunnelEnv, true).catch((error: unknown) => options.log(` the undo of the environment of ztunnel failed: ${String(error)}`));\n }\n }\n } finally {\n if (loader !== null) await loader.stop({ timeout: 0 }).catch(() => undefined);\n if (lab !== null) await lab.close();\n await cluster.delete();\n options.log(`cluster ${s.name} deleted`);\n }\n }\n for (const cell of group) {\n const entry = partial.get(cell.name);\n if (entry === undefined) continue;\n const result: KindCellResult = { cell, ...entry, verdict: judgeKind(cell, entry.runs), at: new Date().toISOString() };\n writeResult(options.out, result);\n rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env0.json`), { force: true });\n for (let env = 1; env < environments; env += 1) rmSync(join(options.out, \"partial\", `${slug(cell.name)}.env${env}.json`), { force: true });\n options.log(`${cell.name}: ${result.verdict.decision}, ${result.verdict.why}`);\n results.push(result);\n }\n }\n return results;\n};\n\n/** This function gives the estimate of a cell in seconds, for the arithmetic of the shards. */\nconst estimate = (cell: KindCell, environments = 2): number => {\n // the span of the faults: the hold, then the rest and the hold of each flap\n const hold = valueIn(faultSpan(cell.trigger), \"s\");\n const trigger: Record<string, number> = {\n \"liveness-restart\": 100,\n crash: 30,\n \"app-stall\": hold + 40,\n \"cpu-throttle\": hold + 40,\n rollout: hold + 50,\n \"authorization-policy\": hold + 20,\n idle: hold + 5,\n // the grace of 30 s of the chart of ztunnel, then the removal of the old pods\n \"ztunnel-restart\": 40,\n // the hold, and a read of the certificates of each ztunnel pod at the fault and at the heal\n \"certificate-expiry\": hold + 20,\n \"cannot-serve\": hold + 20,\n // the hold, then the kubelet of the lost node connects again and the replica is ready again\n \"zone-loss\": hold + 45,\n // the pauses of a speaker (SYS-57): the schedule, then a rejoin of memberlist\n ...(cell.vip === undefined ? {} : { \"freeze-process\": hold + 20 }),\n };\n // a window can need a longer observation: from the end of the hold, until each request that started in it ended\n const after = observationAfter(cell);\n const observed = after === null ? 0 : hold + valueIn(after, \"s\");\n // the band of `ends` too: until each socket can end\n const closes = cell.prediction.kind === \"ends\" && cell.prediction.band !== null ? valueIn(cell.prediction.band.hi, \"s\") + 3 : 0;\n // a quiet black hole: the stop of the calls before the fault, the hold, and the start of the calls after the clear\n const quiet = cell.trigger.quiet === true ? hold + 10 : 0;\n // a VIP moves back to the node of the lost zone before each run: a release or a rejoin, and a gratuitous ARP\n const move = cell.vip === undefined ? 0 : 30;\n // a broker: the hold waits for the broker of the last run, and the producers come back in up to approximately 90 s\n const broker = cell.pulsar === undefined ? 0 : 150;\n const each = 25 + move + broker + Math.max(observed, closes, quiet, trigger[cell.trigger.kind] ?? (cell.template === \"T18\" ? hold + 35 : 15 + valueIn(cell.pod.preStop, \"s\")));\n // the install of Istio in each cluster takes 1 to 2 min, and the install of an announcer approximately 1 min\n // and the install of Pulsar approximately 4 min, with the load of its image\n const setup = (cell.mesh === undefined ? 60 : 180) + (cell.vip === undefined ? 0 : 60) + (cell.pulsar === undefined ? 0 : 240);\n return setup * environments + (cell.runs + environments) * each;\n};\n\n/**\n * This function gives an estimate of the time of a cell: its setup, then each run and each\n * warm-up. A run waits for the cell at rest, samples the load, does the trigger and waits until the\n * cell is at rest again.\n */\nexport const estimatedTime = (cell: KindCell, environments = 2): Quantity => ({ value: estimate(cell, environments), unit: \"s\" });\n\n/**\n * This function gives the share of a shard of a catalog: cells in sequence. Thus the cells of one\n * mode and one mesh flavor stay together and share clusters. Each shard gets an equal share of the\n * estimated time. In one mode, the cells with no mesh come first, in the sequence of the catalog.\n */\nexport const shardOf = <T extends KindCell>(cells: readonly T[], index: number, count: number): T[] => {\n if (!(Number.isInteger(index) && Number.isInteger(count) && count >= 1 && index >= 0 && index < count))\n throw new RangeError(`shard ${index} of ${count} is not a shard`);\n const rank = (cell: KindCell): string => `${cell.mode}/${meshKey(cell) === \"none\" ? \"\" : meshKey(cell)}`;\n const ordered = [...cells].sort((a, b) => (rank(a) === rank(b) ? 0 : rank(a) < rank(b) ? -1 : 1));\n const total = ordered.reduce((sum, cell) => sum + estimate(cell), 0);\n const out: T[] = [];\n let before = 0;\n for (const cell of ordered) {\n const middle = before + estimate(cell) / 2;\n if (Math.min(count - 1, Math.floor((middle / total) * count)) === index) out.push(cell);\n before += estimate(cell);\n }\n return out;\n};\n"],"mappings":"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"}
|