@mark1russell7/kind 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +157 -8
- package/build/index.js +720 -44
- package/build/index.js.map +1 -1
- package/build/{judge-DKrgfhAt.js → judge-C6XgY2Pk.js} +335 -5
- package/build/judge-C6XgY2Pk.js.map +1 -0
- package/build/{judge-DhLShDUj.d.ts → judge-Cp3MzMk1.d.ts} +184 -3
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/package.json +2 -2
- package/build/judge-DKrgfhAt.js.map +0 -1
package/build/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as rulesOf, A as judgeReconnects, At as overshootBand, B as emptyOf, C as observationEnd, Ct as PORT, D as reusesReset, Dt as backlogEnd, E as referenceOf, Et as VIP_ANNOTATION, F as backendsAt, G as outcomesOf, H as intakeOf, I as byClients, J as podsOf, K as periodOf, L as byNode, M as triesOf, Mt as seconds, N as HBONE_PORT, Nt as secondsOf, O as unitOf, Ot as bandOf, P as TCP_STATE, Pt as showBand, Q as restartsAt, R as deletionOf, S as observationAfter, St as OUTSIDE, T as propagationSource, Tt as SERVER, U as isTunnelState, V as failuresOf, W as kernelCounts, X as requestsOf, Y as recordsOf, Z as restartOf, _ as checkRun, _t as quantileOf, a as calibrationOf, at as sliceReadyAt, b as factsOf, bt as LB_CLASS, c as predictionText, ct as socketsOf, d as FORMAT_V1, dt as tunnelsOf, et as sessionsAt, f as judge, ft as ztunnelPodsOf, g as LAST_SLACK, gt as middle, h as KIND_NOISE, ht as medianInterval, i as readRecord, it as sliceEmptyOf, j as latestReturn, jt as requestBand, k as updatesOf, kt as drainBand, l as timeSpread, lt as stoppingOf, m as upgradeCell, mt as RELATIVE, n as kindCellRecordSchema, nt as signalsOf, o as cellsTable, ot as sliceReadyOf, p as upgrade, pt as ztunnelReplaced, q as podsAt, r as kindCellRecordV1Schema, rt as sliceDropOf, s as medianText, st as slicesOf, t as STAGES, tt as signalOf, u as FORMAT, ut as transitionOf, v as cyclesOf, vt as CLIENT, w as propagationOf, wt as REDIS, x as judgeKind, xt as LOG_LABEL, y as deletionsOf, yt as HEALTH_PORT, z as dropOf } from "./judge-C6XgY2Pk.js";
|
|
2
2
|
import { valueIn } from "@mark1russell7/quantity";
|
|
3
3
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -10,7 +10,7 @@ import { gunzipSync } from "node:zlib";
|
|
|
10
10
|
import { failSpan, lint, lintStop, recoverSpan, service } from "@mark1russell7/service";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
|
-
import { harnessImage, imageOf } from "@mark1russell7/harness";
|
|
13
|
+
import { harnessImage, imageOf, substrateKey, substrateOf, substrateOn } from "@mark1russell7/harness";
|
|
14
14
|
import { redisClient } from "@mark1russell7/service/redis";
|
|
15
15
|
import { DRAIN_RULES, DRAIN_RULES as DRAIN_RULES$1, O04, O09, O10, O16 } from "@mark1russell7/service/rules";
|
|
16
16
|
//#region src/backends.ts
|
|
@@ -1375,7 +1375,7 @@ const stepsOf = (cell) => {
|
|
|
1375
1375
|
case "crash": return {
|
|
1376
1376
|
inject: [{
|
|
1377
1377
|
do: "signal",
|
|
1378
|
-
target: "server",
|
|
1378
|
+
target: cell.pulsar === void 0 ? "server" : "broker",
|
|
1379
1379
|
signal: "SIGKILL"
|
|
1380
1380
|
}],
|
|
1381
1381
|
hold: null,
|
|
@@ -1393,6 +1393,24 @@ const stepsOf = (cell) => {
|
|
|
1393
1393
|
restore: []
|
|
1394
1394
|
};
|
|
1395
1395
|
case "freeze-process":
|
|
1396
|
+
if (cell.vip !== void 0) return {
|
|
1397
|
+
inject: [{
|
|
1398
|
+
do: "signal",
|
|
1399
|
+
target: "speaker",
|
|
1400
|
+
signal: "SIGSTOP"
|
|
1401
|
+
}],
|
|
1402
|
+
hold: needs(trigger.hold, "a hold, the time that the speaker is frozen"),
|
|
1403
|
+
heal: [{
|
|
1404
|
+
do: "signal",
|
|
1405
|
+
target: "speaker",
|
|
1406
|
+
signal: "SIGCONT"
|
|
1407
|
+
}],
|
|
1408
|
+
restore: [{
|
|
1409
|
+
do: "signal",
|
|
1410
|
+
target: "speaker",
|
|
1411
|
+
signal: "SIGCONT"
|
|
1412
|
+
}]
|
|
1413
|
+
};
|
|
1396
1414
|
if (cell.template === "T07") return {
|
|
1397
1415
|
inject: [
|
|
1398
1416
|
{
|
|
@@ -1814,7 +1832,7 @@ const deletes = (cell) => [
|
|
|
1814
1832
|
"rolling-update",
|
|
1815
1833
|
"node-drain",
|
|
1816
1834
|
"scale-in"
|
|
1817
|
-
].includes(cell.trigger.kind) || cell.template === "T07" && cell.trigger.kind === "freeze-process";
|
|
1835
|
+
].includes(cell.trigger.kind) || cell.template === "T07" && cell.trigger.kind === "freeze-process" && cell.vip === void 0;
|
|
1818
1836
|
//#endregion
|
|
1819
1837
|
//#region src/image.ts
|
|
1820
1838
|
/** This folder is the root of the package, and its `image/` folder has the files of the image. */
|
|
@@ -2511,7 +2529,7 @@ const vipOf = (subnet) => {
|
|
|
2511
2529
|
0
|
|
2512
2530
|
].map((shift) => Math.floor(address / 2 ** shift) % 256).join(".");
|
|
2513
2531
|
};
|
|
2514
|
-
const sleep$
|
|
2532
|
+
const sleep$3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2515
2533
|
/** This function gives the pods of kube-vip that are not deleted. */
|
|
2516
2534
|
const kubeVipPods = async (cluster) => {
|
|
2517
2535
|
return JSON.parse(await cluster.kubectl([
|
|
@@ -2540,7 +2558,7 @@ const until$1 = async (within, check) => {
|
|
|
2540
2558
|
const end = Date.now() + within;
|
|
2541
2559
|
let why = await check();
|
|
2542
2560
|
while (why !== null && Date.now() < end) {
|
|
2543
|
-
await sleep$
|
|
2561
|
+
await sleep$3(500);
|
|
2544
2562
|
why = await check();
|
|
2545
2563
|
}
|
|
2546
2564
|
return why;
|
|
@@ -2657,8 +2675,8 @@ const vipServed = (cluster, namespace, service, vip, within = 12e4) => until$1(w
|
|
|
2657
2675
|
* a usable speaker (speaker/layer2_controller.go, lines 83–129). A usable speaker is a member of
|
|
2658
2676
|
* memberlist that is not dead (internal/speakerlist/speakerlist.go, lines 303–320).
|
|
2659
2677
|
* - The speakers start memberlist from `DefaultLANConfig` (speakerlist.go, line 72). A file
|
|
2660
|
-
* /etc/metallb/memberlist/config.json replaces that config (lines 77–84).
|
|
2661
|
-
* such file
|
|
2678
|
+
* /etc/metallb/memberlist/config.json replaces that config (lines 77–84). Without settings of
|
|
2679
|
+
* memberlist, the install makes no such file. Then each value is the default of memberlist v0.5.1.
|
|
2662
2680
|
* - Each event of memberlist (a join, a leave or an update) calls `ForceSync` (speakerlist.go, lines
|
|
2663
2681
|
* 339–349). `ForceSync` reconciles each Service at once, with no delay (internal/k8s/k8s.go, lines
|
|
2664
2682
|
* 181–184, and internal/k8s/controllers/service_controller_reload.go, lines 62–118).
|
|
@@ -2719,6 +2737,60 @@ const MEMBERLIST = {
|
|
|
2719
2737
|
unit: "ms"
|
|
2720
2738
|
}
|
|
2721
2739
|
};
|
|
2740
|
+
/**
|
|
2741
|
+
* These are the values of the config file of memberlist, as `DefaultLANConfig` of v0.5.1 gives them
|
|
2742
|
+
* (config.go, lines 302–341). The speaker needs each field of the file, and it reads a time as a
|
|
2743
|
+
* duration of Go (speakerlist.go, lines 355–432). The chart mounts the ConfigMap
|
|
2744
|
+
* `metallb-memberlist-config` at /etc/metallb/memberlist, if the ConfigMap exists
|
|
2745
|
+
* (charts/metallb/templates/speaker.yaml, lines 195–198 and 391–392). The key is `config.json`.
|
|
2746
|
+
*/
|
|
2747
|
+
const MEMBERLIST_FILE = {
|
|
2748
|
+
TCPTimeout: "10s",
|
|
2749
|
+
IndirectChecks: 3,
|
|
2750
|
+
RetransmitMult: 4,
|
|
2751
|
+
SuspicionMult: 4,
|
|
2752
|
+
SuspicionMaxTimeoutMult: 6,
|
|
2753
|
+
PushPullInterval: "30s",
|
|
2754
|
+
ProbeTimeout: "500ms",
|
|
2755
|
+
ProbeInterval: "1s",
|
|
2756
|
+
DisableTcpPings: false,
|
|
2757
|
+
AwarenessMaxMultiplier: 8,
|
|
2758
|
+
GossipNodes: 3,
|
|
2759
|
+
GossipInterval: "200ms",
|
|
2760
|
+
GossipToTheDeadTime: "30s",
|
|
2761
|
+
GossipVerifyIncoming: true,
|
|
2762
|
+
GossipVerifyOutgoing: true,
|
|
2763
|
+
EnableCompression: true,
|
|
2764
|
+
HandoffQueueDepth: 1024,
|
|
2765
|
+
UDPBufferSize: 1400,
|
|
2766
|
+
QueueCheckInterval: "30s"
|
|
2767
|
+
};
|
|
2768
|
+
/** This name is the ConfigMap of the config file of memberlist. */
|
|
2769
|
+
const MEMBERLIST_CONFIG = "metallb-memberlist-config";
|
|
2770
|
+
/** This function gives a time as a duration of Go in whole milliseconds, for example "500ms". */
|
|
2771
|
+
const goDuration$1 = (time) => {
|
|
2772
|
+
const ms = valueIn(time, "ms");
|
|
2773
|
+
if (!Number.isInteger(ms) || ms <= 0) throw new RangeError(`a time of memberlist is a whole count of milliseconds above zero, not ${ms} ms`);
|
|
2774
|
+
return `${ms}ms`;
|
|
2775
|
+
};
|
|
2776
|
+
/** This function gives the config file of memberlist with `settings`. Each other value is the default of `MEMBERLIST_FILE`. */
|
|
2777
|
+
const memberlistFileOf = (settings) => {
|
|
2778
|
+
if (!Number.isInteger(settings.suspicionMult) || settings.suspicionMult < 1) throw new RangeError(`SuspicionMult is a whole number of 1 or more, not ${settings.suspicionMult}`);
|
|
2779
|
+
if (valueIn(settings.probeTimeout, "ms") >= valueIn(settings.probeInterval, "ms")) throw new RangeError("the probe timeout of memberlist is less than its probe interval");
|
|
2780
|
+
return {
|
|
2781
|
+
...MEMBERLIST_FILE,
|
|
2782
|
+
ProbeInterval: goDuration$1(settings.probeInterval),
|
|
2783
|
+
ProbeTimeout: goDuration$1(settings.probeTimeout),
|
|
2784
|
+
SuspicionMult: settings.suspicionMult
|
|
2785
|
+
};
|
|
2786
|
+
};
|
|
2787
|
+
/** This function gives the terms of memberlist with `settings`, or the defaults (`MEMBERLIST`) without them. */
|
|
2788
|
+
const memberlistTermsOf = (settings) => settings === void 0 ? MEMBERLIST : {
|
|
2789
|
+
...MEMBERLIST,
|
|
2790
|
+
probeInterval: settings.probeInterval,
|
|
2791
|
+
probeTimeout: settings.probeTimeout,
|
|
2792
|
+
suspicionMult: settings.suspicionMult
|
|
2793
|
+
};
|
|
2722
2794
|
/** 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). */
|
|
2723
2795
|
const METALLB_SPAM = {
|
|
2724
2796
|
every: {
|
|
@@ -2829,13 +2901,13 @@ const metallbVipOf = (subnet, owner, nodes) => {
|
|
|
2829
2901
|
}
|
|
2830
2902
|
throw new Error(`the subnet ${subnet} has no VIP that ${owner} owns`);
|
|
2831
2903
|
};
|
|
2832
|
-
const sleep$
|
|
2904
|
+
const sleep$2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2833
2905
|
/** This function waits until a condition gives null, and gives null, or the last reason after `within` ms. */
|
|
2834
2906
|
const until = async (within, check) => {
|
|
2835
2907
|
const end = Date.now() + within;
|
|
2836
2908
|
let why = await check();
|
|
2837
2909
|
while (why !== null && Date.now() < end) {
|
|
2838
|
-
await sleep$
|
|
2910
|
+
await sleep$2(500);
|
|
2839
2911
|
why = await check();
|
|
2840
2912
|
}
|
|
2841
2913
|
return why;
|
|
@@ -2902,8 +2974,12 @@ const holdMetallb = async (cluster, owner, nodes, namespace, service, within = 1
|
|
|
2902
2974
|
* and a pool of the VIP. The webhook of the controller checks the pool, thus the function tries the
|
|
2903
2975
|
* pool again until the webhook answers. A failed install gives the pods of MetalLB and the tail of
|
|
2904
2976
|
* their logs.
|
|
2977
|
+
*
|
|
2978
|
+
* With `memberlist`, the ConfigMap of the config file comes before the speakers. Then the function
|
|
2979
|
+
* checks that each speaker logged the load of the file (speakerlist.go, line 393). Without it, the
|
|
2980
|
+
* function checks that the ConfigMap does not exist.
|
|
2905
2981
|
*/
|
|
2906
|
-
const installMetallb = async (cluster, images, nodes, vip) => {
|
|
2982
|
+
const installMetallb = async (cluster, images, nodes, vip, memberlist) => {
|
|
2907
2983
|
try {
|
|
2908
2984
|
for (const node of nodes) await cluster.kubectl([
|
|
2909
2985
|
"label",
|
|
@@ -2912,24 +2988,66 @@ const installMetallb = async (cluster, images, nodes, vip) => {
|
|
|
2912
2988
|
`${VIP_LABEL}=1`,
|
|
2913
2989
|
"--overwrite"
|
|
2914
2990
|
]);
|
|
2991
|
+
if (memberlist !== void 0) {
|
|
2992
|
+
await cluster.apply([{
|
|
2993
|
+
apiVersion: "v1",
|
|
2994
|
+
kind: "Namespace",
|
|
2995
|
+
metadata: { name: METALLB_NAMESPACE }
|
|
2996
|
+
}]);
|
|
2997
|
+
await cluster.apply([{
|
|
2998
|
+
apiVersion: "v1",
|
|
2999
|
+
kind: "ConfigMap",
|
|
3000
|
+
metadata: {
|
|
3001
|
+
name: MEMBERLIST_CONFIG,
|
|
3002
|
+
namespace: METALLB_NAMESPACE
|
|
3003
|
+
},
|
|
3004
|
+
data: { "config.json": JSON.stringify(memberlistFileOf(memberlist)) }
|
|
3005
|
+
}]);
|
|
3006
|
+
}
|
|
2915
3007
|
await helmInstall(cluster, "metallb", METALLB_CHART, METALLB_NAMESPACE, metallbValues(images), { createNamespace: true });
|
|
2916
3008
|
let failure = null;
|
|
2917
3009
|
const end = Date.now() + 18e4;
|
|
2918
3010
|
do {
|
|
2919
3011
|
failure = await cluster.apply(metallbObjects(vip)).then(() => null, (error) => error);
|
|
2920
|
-
if (failure !== null) await sleep$
|
|
3012
|
+
if (failure !== null) await sleep$2(2e3);
|
|
2921
3013
|
} while (failure !== null && Date.now() < end);
|
|
2922
3014
|
if (failure !== null) throw failure;
|
|
2923
|
-
if (
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
3015
|
+
if (memberlist === void 0) {
|
|
3016
|
+
if ((await cluster.kubectl([
|
|
3017
|
+
"-n",
|
|
3018
|
+
"metallb-system",
|
|
3019
|
+
"get",
|
|
3020
|
+
"configmap",
|
|
3021
|
+
MEMBERLIST_CONFIG,
|
|
3022
|
+
"--ignore-not-found",
|
|
3023
|
+
"-o",
|
|
3024
|
+
"name"
|
|
3025
|
+
])).trim() !== "") throw new Error(`the ConfigMap ${MEMBERLIST_CONFIG} is there: memberlist would not use its defaults`);
|
|
3026
|
+
} else {
|
|
3027
|
+
const loaded = await until(12e4, async () => {
|
|
3028
|
+
const pods = JSON.parse(await cluster.kubectl([
|
|
3029
|
+
"-n",
|
|
3030
|
+
METALLB_NAMESPACE,
|
|
3031
|
+
"get",
|
|
3032
|
+
"pods",
|
|
3033
|
+
"-l",
|
|
3034
|
+
"app.kubernetes.io/component=speaker",
|
|
3035
|
+
"-o",
|
|
3036
|
+
"json"
|
|
3037
|
+
]));
|
|
3038
|
+
if (pods.items.length !== nodes.length) return `${pods.items.length} speakers, not ${nodes.length}`;
|
|
3039
|
+
for (const pod of pods.items) if (!(await cluster.kubectl([
|
|
3040
|
+
"-n",
|
|
3041
|
+
"metallb-system",
|
|
3042
|
+
"logs",
|
|
3043
|
+
pod.metadata.name,
|
|
3044
|
+
"-c",
|
|
3045
|
+
"speaker"
|
|
3046
|
+
]).catch((error) => String(error))).includes("loading memberlist config from file")) return `the speaker ${pod.metadata.name} did not log the load of the config file of memberlist`;
|
|
3047
|
+
return null;
|
|
3048
|
+
});
|
|
3049
|
+
if (loaded !== null) throw new Error(loaded);
|
|
3050
|
+
}
|
|
2933
3051
|
} catch (error) {
|
|
2934
3052
|
const state = async (args) => (await cluster.kubectl([
|
|
2935
3053
|
"-n",
|
|
@@ -2954,7 +3072,7 @@ const installMetallb = async (cluster, images, nodes, vip) => {
|
|
|
2954
3072
|
}
|
|
2955
3073
|
};
|
|
2956
3074
|
/** These words mark the lines of a speaker that the record keeps: the events of memberlist, the announces and the withdraws. */
|
|
2957
|
-
const KEPT = /node event|Marking|Suspect|Refuting|serviceAnnounced|serviceWithdrawn|memberlist/;
|
|
3075
|
+
const KEPT$1 = /node event|Marking|Suspect|Refuting|serviceAnnounced|serviceWithdrawn|memberlist/;
|
|
2958
3076
|
/**
|
|
2959
3077
|
* This function gives the record of MetalLB after a run: the nodes that announce the Service, and
|
|
2960
3078
|
* some lines of each speaker. The lines are about memberlist and the announces, with the times of the kubelet.
|
|
@@ -2974,7 +3092,7 @@ const metallbRecord = async (cluster, namespace, service) => {
|
|
|
2974
3092
|
"--tail=4000",
|
|
2975
3093
|
"--prefix",
|
|
2976
3094
|
"--timestamps"
|
|
2977
|
-
]).catch((error) => String(error))).split("\n").filter((line) => KEPT.test(line)).slice(-
|
|
3095
|
+
]).catch((error) => String(error))).split("\n").filter((line) => KEPT$1.test(line)).slice(-600).join("\n").slice(-9e4)
|
|
2978
3096
|
};
|
|
2979
3097
|
};
|
|
2980
3098
|
//#endregion
|
|
@@ -3025,10 +3143,10 @@ const METALLB_ANNOUNCER = {
|
|
|
3025
3143
|
},
|
|
3026
3144
|
settle: METALLB_SPAM.for,
|
|
3027
3145
|
vipOf: (subnet, owner, nodes) => metallbVipOf(subnet, owner, nodes),
|
|
3028
|
-
install: (cluster, images, _owner, nodes, vip) => installMetallb(cluster, {
|
|
3146
|
+
install: (cluster, images, _owner, nodes, vip, settings) => installMetallb(cluster, {
|
|
3029
3147
|
speaker: local(images, "metallb-speaker"),
|
|
3030
3148
|
controller: local(images, "metallb-controller")
|
|
3031
|
-
}, nodes, vip),
|
|
3149
|
+
}, nodes, vip, settings?.memberlist),
|
|
3032
3150
|
hold: (cluster, owner, nodes, namespace, service) => holdMetallb(cluster, owner, nodes, namespace, service),
|
|
3033
3151
|
record: (cluster, namespace, service) => metallbRecord(cluster, namespace, service)
|
|
3034
3152
|
};
|
|
@@ -3041,6 +3159,451 @@ const macsAt = (found, mac, count) => {
|
|
|
3041
3159
|
return wrong.length === 0 ? null : wrong.map((each) => `${each.client} has ${each.mac ?? "no MAC"}`).join(", ");
|
|
3042
3160
|
};
|
|
3043
3161
|
//#endregion
|
|
3162
|
+
//#region src/pulsar.ts
|
|
3163
|
+
/**
|
|
3164
|
+
* This module installs Apache Pulsar 5.0.0 in a kind cluster (SYS-56): one ZooKeeper, one bookie and
|
|
3165
|
+
* two brokers, from the Helm chart 4.8.0. It also gives the producers of the cell: a Python script
|
|
3166
|
+
* in the image of Pulsar, with its client 3.13.0 over the C++ client 4.2.0. Each producer sends to
|
|
3167
|
+
* one topic, and a thread of it looks up the owner of that topic at each probe.
|
|
3168
|
+
*
|
|
3169
|
+
* The fault is a SIGKILL of the JVM of the broker that owns the topic. Each term of the return of a
|
|
3170
|
+
* producer comes from a pinned source:
|
|
3171
|
+
* - The owner of a bundle is an ephemeral znode of the session of its broker. A lookup gives the
|
|
3172
|
+
* owner from the cache of the ownership, with no check of its life (pulsar-broker
|
|
3173
|
+
* NamespaceService.java at v5.0.0, lines 489–508). Thus each lookup names the stopped broker
|
|
3174
|
+
* until its session expires.
|
|
3175
|
+
* - The session timeout is `metadataStoreSessionTimeoutMillis`, 30000 (conf/broker.conf at v5.0.0,
|
|
3176
|
+
* line 187, and the chart, values.yaml line 1474). ZKMetadataStore.java gives it to ZooKeeper (line 105).
|
|
3177
|
+
* - The client of ZooKeeper 3.9.6 pings after 9 s with no send (ClientCnxn.java, lines 422, 1184
|
|
3178
|
+
* and 1263–1268). That is half of the read timeout of 2/3 of the session, less 1 s.
|
|
3179
|
+
* The server rounds each expiry up to the next tick of 2000 ms (ExpiryQueue.java, lines 53–54
|
|
3180
|
+
* and 87, and conf/zookeeper.conf of Pulsar, line 21). Thus the session expires 21–32 s after the stop.
|
|
3181
|
+
* - The broker that stays then takes the bundle at the next lookup.
|
|
3182
|
+
* - The chart starts a broker again only after its old znode is gone (templates/broker-statefulset.yaml,
|
|
3183
|
+
* lines 296–302). Thus the stopped broker cannot take the bundle back first.
|
|
3184
|
+
*/
|
|
3185
|
+
/** This version is the release of Pulsar of the lab. */
|
|
3186
|
+
const PULSAR_VERSION = "5.0.0";
|
|
3187
|
+
/** This chart is the Helm chart 4.8.0 of Pulsar. The sha256 is of the file of the release. */
|
|
3188
|
+
const PULSAR_CHART = {
|
|
3189
|
+
name: "pulsar",
|
|
3190
|
+
version: "4.8.0",
|
|
3191
|
+
url: "https://downloads.apache.org/pulsar/helm-chart/4.8.0/pulsar-4.8.0.tgz",
|
|
3192
|
+
sha256: "464f4a268463d187c9bc83b365b31de0b4a460533d948a04f846ba9b026068f5"
|
|
3193
|
+
};
|
|
3194
|
+
/**
|
|
3195
|
+
* This image is Pulsar 5.0.0 for linux/amd64, pinned by the digest of the manifest of the platform.
|
|
3196
|
+
* It has ZooKeeper 3.9.6, BookKeeper, the broker, Python 3 and the Python client 3.13.0. The lab
|
|
3197
|
+
* pulls it by its digest, tags it (`localPin`), and loads it into each node.
|
|
3198
|
+
*/
|
|
3199
|
+
const PULSAR_IMAGE = "apachepulsar/pulsar:5.0.0@sha256:11e9a885121e16be91da77fdd3cfc3d4cbbece4700271d75e949585dbd3a5a5b";
|
|
3200
|
+
/** This namespace holds Pulsar. The release of the chart is `pulsar` too. */
|
|
3201
|
+
const PULSAR_NAMESPACE = "pulsar";
|
|
3202
|
+
/** This name is the app of the producers of a cell. */
|
|
3203
|
+
const PRODUCER = "producer";
|
|
3204
|
+
/** This topic is the topic of the producers. */
|
|
3205
|
+
const PULSAR_TOPIC = "persistent://public/default/lab";
|
|
3206
|
+
/** This URL is the Service of the brokers for the binary protocol. */
|
|
3207
|
+
const PULSAR_SERVICE = `pulsar://pulsar-broker.${PULSAR_NAMESPACE}.svc.cluster.local:6650`;
|
|
3208
|
+
/** This URL is the lookup of the owner of the topic, on the Service of the brokers. */
|
|
3209
|
+
const PULSAR_LOOKUP = `http://pulsar-broker.${PULSAR_NAMESPACE}.svc.cluster.local:8080/lookup/v2/topic/persistent/public/default/lab`;
|
|
3210
|
+
/** This time is the session timeout of each broker in ZooKeeper (`metadataStoreSessionTimeoutMillis`). */
|
|
3211
|
+
const SESSION = {
|
|
3212
|
+
value: 30,
|
|
3213
|
+
unit: "s"
|
|
3214
|
+
};
|
|
3215
|
+
/** This time is the tick of ZooKeeper (`tickTime`). The server rounds each expiry up to a tick. */
|
|
3216
|
+
const TICK = {
|
|
3217
|
+
value: 2,
|
|
3218
|
+
unit: "s"
|
|
3219
|
+
};
|
|
3220
|
+
/** 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. */
|
|
3221
|
+
const PING = {
|
|
3222
|
+
value: 9,
|
|
3223
|
+
unit: "s"
|
|
3224
|
+
};
|
|
3225
|
+
/**
|
|
3226
|
+
* This backoff is the backoff of a producer of the C++ client 4.2.0, with the defaults of the Python
|
|
3227
|
+
* client 3.13.0:
|
|
3228
|
+
* - The initial delay is 100 ms and the most is 60 s (lib/ClientConfigurationImpl.h, lines 39–40).
|
|
3229
|
+
* - The mandatory stop is the send timeout less 100 ms, and at least 100 ms (lib/ProducerImpl.cc,
|
|
3230
|
+
* lines 55–57). The send timeout of `create_producer` is 30000 ms (pulsar/__init__.py, line 1015).
|
|
3231
|
+
* - The jitter takes 0–9 % of each delay (lib/Backoff.cc, line 54).
|
|
3232
|
+
* - A try that fails takes the lookup and the connect, which a reset ends at once. The lab gives it 100 ms.
|
|
3233
|
+
*/
|
|
3234
|
+
const PRODUCER_BACKOFF = {
|
|
3235
|
+
initial: {
|
|
3236
|
+
value: 100,
|
|
3237
|
+
unit: "ms"
|
|
3238
|
+
},
|
|
3239
|
+
max: {
|
|
3240
|
+
value: 60,
|
|
3241
|
+
unit: "s"
|
|
3242
|
+
},
|
|
3243
|
+
mandatoryStop: {
|
|
3244
|
+
value: 29.9,
|
|
3245
|
+
unit: "s"
|
|
3246
|
+
},
|
|
3247
|
+
jitter: .09,
|
|
3248
|
+
attempt: {
|
|
3249
|
+
value: 100,
|
|
3250
|
+
unit: "ms"
|
|
3251
|
+
}
|
|
3252
|
+
};
|
|
3253
|
+
/** This function gives the repository and the tag of a local image, for the values of a chart. */
|
|
3254
|
+
const repositoryOf = (local) => {
|
|
3255
|
+
const at = local.lastIndexOf(":");
|
|
3256
|
+
if (at <= 0) throw new Error(`the image ${local} has no tag`);
|
|
3257
|
+
return {
|
|
3258
|
+
repository: local.slice(0, at),
|
|
3259
|
+
tag: local.slice(at + 1)
|
|
3260
|
+
};
|
|
3261
|
+
};
|
|
3262
|
+
/**
|
|
3263
|
+
* This function gives the values of the chart: ZooKeeper, one bookie and two brokers, with no
|
|
3264
|
+
* proxy, no autorecovery, no toolset and no monitor. The bookie keeps one copy of each entry, and
|
|
3265
|
+
* the brokers do not move a bundle by load. Each image is the local image, with no pull.
|
|
3266
|
+
*/
|
|
3267
|
+
const pulsarValues = (image) => {
|
|
3268
|
+
const { repository, tag } = repositoryOf(image);
|
|
3269
|
+
const noMonitor = { podMonitor: { enabled: false } };
|
|
3270
|
+
return {
|
|
3271
|
+
components: {
|
|
3272
|
+
zookeeper: true,
|
|
3273
|
+
bookkeeper: true,
|
|
3274
|
+
broker: true,
|
|
3275
|
+
autorecovery: false,
|
|
3276
|
+
proxy: false,
|
|
3277
|
+
toolset: false,
|
|
3278
|
+
functions: false,
|
|
3279
|
+
function_worker: false
|
|
3280
|
+
},
|
|
3281
|
+
"victoria-metrics-k8s-stack": { enabled: false },
|
|
3282
|
+
defaultPulsarImageRepository: repository,
|
|
3283
|
+
defaultPulsarImageTag: tag,
|
|
3284
|
+
defaultPullPolicy: "Never",
|
|
3285
|
+
affinity: { anti_affinity: false },
|
|
3286
|
+
volumes: { persistence: false },
|
|
3287
|
+
zookeeper: {
|
|
3288
|
+
replicaCount: 1,
|
|
3289
|
+
statefulsetUpgrade: { enabled: false },
|
|
3290
|
+
...noMonitor
|
|
3291
|
+
},
|
|
3292
|
+
bookkeeper: {
|
|
3293
|
+
replicaCount: 1,
|
|
3294
|
+
...noMonitor
|
|
3295
|
+
},
|
|
3296
|
+
autorecovery: noMonitor,
|
|
3297
|
+
proxy: noMonitor,
|
|
3298
|
+
broker: {
|
|
3299
|
+
replicaCount: 2,
|
|
3300
|
+
statefulsetUpgrade: { enabled: false },
|
|
3301
|
+
...noMonitor,
|
|
3302
|
+
configData: {
|
|
3303
|
+
managedLedgerDefaultEnsembleSize: "1",
|
|
3304
|
+
managedLedgerDefaultWriteQuorum: "1",
|
|
3305
|
+
managedLedgerDefaultAckQuorum: "1",
|
|
3306
|
+
loadBalancerSheddingEnabled: "false",
|
|
3307
|
+
loadBalancerAutoBundleSplitEnabled: "false"
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
};
|
|
3311
|
+
};
|
|
3312
|
+
/**
|
|
3313
|
+
* This script is a producer of the cell. Each line that it writes is JSON, with the time of
|
|
3314
|
+
* CLOCK_MONOTONIC in milliseconds, the pod and the node:
|
|
3315
|
+
* - `send`: one send, at its start, with its time to the answer and its outcome. A send is `ok` within
|
|
3316
|
+
* the timeout, `late` after it, or the name of the result of the client (for example `timeout`).
|
|
3317
|
+
* - `lookup`: one lookup of the owner of the topic, with the host of the owner.
|
|
3318
|
+
* - `pulsar`: one line of the C++ client about the connection of the producer.
|
|
3319
|
+
*/
|
|
3320
|
+
const PRODUCER_SCRIPT = `import json, logging, os, sys, threading, time, urllib.request
|
|
3321
|
+
import pulsar
|
|
3322
|
+
|
|
3323
|
+
POD = os.environ["POD_NAME"]
|
|
3324
|
+
NODE = os.environ["NODE_NAME"]
|
|
3325
|
+
EVERY = int(os.environ["EVERY_MS"]) / 1000
|
|
3326
|
+
TIMEOUT = int(os.environ["TIMEOUT_MS"])
|
|
3327
|
+
PROBE = int(os.environ["PROBE_MS"]) / 1000
|
|
3328
|
+
LOCK = threading.Lock()
|
|
3329
|
+
KEPT = ("Schedule reconnection", "Getting connection from pool", "Created producer", "Connection closed", "Disconnected", "Lookup response", "Failed to", "Finished connecting")
|
|
3330
|
+
|
|
3331
|
+
|
|
3332
|
+
def now():
|
|
3333
|
+
return time.monotonic() * 1000
|
|
3334
|
+
|
|
3335
|
+
|
|
3336
|
+
def say(**line):
|
|
3337
|
+
text = json.dumps({**line, "p": POD, "n": NODE}, separators=(",", ":"))
|
|
3338
|
+
with LOCK:
|
|
3339
|
+
sys.stdout.write(text + "\\n")
|
|
3340
|
+
sys.stdout.flush()
|
|
3341
|
+
|
|
3342
|
+
|
|
3343
|
+
class Relay(logging.Handler):
|
|
3344
|
+
def emit(self, record):
|
|
3345
|
+
message = record.getMessage()
|
|
3346
|
+
if any(word in message for word in KEPT):
|
|
3347
|
+
say(ev="pulsar", t=now(), m=message[:400])
|
|
3348
|
+
|
|
3349
|
+
|
|
3350
|
+
def probe():
|
|
3351
|
+
while True:
|
|
3352
|
+
start = now()
|
|
3353
|
+
owner = None
|
|
3354
|
+
outcome = "ok"
|
|
3355
|
+
try:
|
|
3356
|
+
with urllib.request.urlopen(os.environ["LOOKUP"], timeout=1) as answer:
|
|
3357
|
+
data = json.loads(answer.read())
|
|
3358
|
+
url = data.get("brokerUrl") or ""
|
|
3359
|
+
owner = url.split("://", 1)[-1].rsplit(":", 1)[0] or None
|
|
3360
|
+
except Exception as error:
|
|
3361
|
+
outcome = type(error).__name__.lower()
|
|
3362
|
+
say(ev="lookup", t=start, d=round(now() - start, 3), o=outcome, owner=owner)
|
|
3363
|
+
time.sleep(max(0.0, PROBE - (now() - start) / 1000))
|
|
3364
|
+
|
|
3365
|
+
|
|
3366
|
+
def answered(start):
|
|
3367
|
+
def done(result, message_id):
|
|
3368
|
+
took = now() - start
|
|
3369
|
+
if result == pulsar.Result.Ok:
|
|
3370
|
+
outcome = "ok" if took <= TIMEOUT else "late"
|
|
3371
|
+
else:
|
|
3372
|
+
outcome = getattr(result, "name", str(result).split(".")[-1]).lower()
|
|
3373
|
+
say(ev="send", t=start, d=round(took, 3), o=outcome)
|
|
3374
|
+
return done
|
|
3375
|
+
|
|
3376
|
+
|
|
3377
|
+
logger = logging.getLogger("lab")
|
|
3378
|
+
logger.setLevel(logging.INFO)
|
|
3379
|
+
logger.propagate = False
|
|
3380
|
+
logger.addHandler(Relay())
|
|
3381
|
+
threading.Thread(target=probe, daemon=True).start()
|
|
3382
|
+
client = pulsar.Client(os.environ["SERVICE"], logger=logger)
|
|
3383
|
+
while True:
|
|
3384
|
+
try:
|
|
3385
|
+
producer = client.create_producer(os.environ["TOPIC"])
|
|
3386
|
+
break
|
|
3387
|
+
except Exception as error:
|
|
3388
|
+
say(ev="text", t=now(), text="create_producer: " + str(error)[:200])
|
|
3389
|
+
time.sleep(2)
|
|
3390
|
+
at = time.monotonic()
|
|
3391
|
+
while True:
|
|
3392
|
+
start = now()
|
|
3393
|
+
try:
|
|
3394
|
+
producer.send_async(b"lab", answered(start))
|
|
3395
|
+
except Exception as error:
|
|
3396
|
+
say(ev="send", t=start, d=0, o="refused-" + type(error).__name__.lower())
|
|
3397
|
+
at += EVERY
|
|
3398
|
+
time.sleep(max(0.0, at - time.monotonic()))
|
|
3399
|
+
`;
|
|
3400
|
+
/**
|
|
3401
|
+
* This function gives the producers of a cell in its namespace. They are the script in a ConfigMap,
|
|
3402
|
+
* and a Deployment of `clients` pods in the local image of Pulsar, one on each worker.
|
|
3403
|
+
*/
|
|
3404
|
+
const pulsarObjects = (cell, namespace, image) => {
|
|
3405
|
+
if (cell.pulsar === void 0) throw new Error(`${cell.name} has no broker`);
|
|
3406
|
+
const env = {
|
|
3407
|
+
SERVICE: PULSAR_SERVICE,
|
|
3408
|
+
TOPIC: PULSAR_TOPIC,
|
|
3409
|
+
LOOKUP: PULSAR_LOOKUP,
|
|
3410
|
+
EVERY_MS: String(valueIn(cell.workload.every, "ms")),
|
|
3411
|
+
TIMEOUT_MS: String(valueIn(cell.workload.timeout, "ms")),
|
|
3412
|
+
PROBE_MS: String(valueIn(cell.pulsar.probe, "ms")),
|
|
3413
|
+
PYTHONUNBUFFERED: "1"
|
|
3414
|
+
};
|
|
3415
|
+
return [{
|
|
3416
|
+
apiVersion: "v1",
|
|
3417
|
+
kind: "ConfigMap",
|
|
3418
|
+
metadata: {
|
|
3419
|
+
name: PRODUCER,
|
|
3420
|
+
namespace
|
|
3421
|
+
},
|
|
3422
|
+
data: { "producer.py": PRODUCER_SCRIPT }
|
|
3423
|
+
}, {
|
|
3424
|
+
apiVersion: "apps/v1",
|
|
3425
|
+
kind: "Deployment",
|
|
3426
|
+
metadata: {
|
|
3427
|
+
name: PRODUCER,
|
|
3428
|
+
namespace,
|
|
3429
|
+
labels: { app: PRODUCER }
|
|
3430
|
+
},
|
|
3431
|
+
spec: {
|
|
3432
|
+
replicas: cell.pulsar.clients,
|
|
3433
|
+
selector: { matchLabels: { app: PRODUCER } },
|
|
3434
|
+
template: {
|
|
3435
|
+
metadata: { labels: {
|
|
3436
|
+
app: PRODUCER,
|
|
3437
|
+
[LOG_LABEL]: "1"
|
|
3438
|
+
} },
|
|
3439
|
+
spec: {
|
|
3440
|
+
terminationGracePeriodSeconds: 0,
|
|
3441
|
+
topologySpreadConstraints: [{
|
|
3442
|
+
maxSkew: 1,
|
|
3443
|
+
topologyKey: "kubernetes.io/hostname",
|
|
3444
|
+
whenUnsatisfiable: "DoNotSchedule",
|
|
3445
|
+
labelSelector: { matchLabels: { app: PRODUCER } }
|
|
3446
|
+
}],
|
|
3447
|
+
volumes: [{
|
|
3448
|
+
name: "script",
|
|
3449
|
+
configMap: { name: PRODUCER }
|
|
3450
|
+
}],
|
|
3451
|
+
containers: [{
|
|
3452
|
+
name: PRODUCER,
|
|
3453
|
+
image,
|
|
3454
|
+
imagePullPolicy: "Never",
|
|
3455
|
+
command: ["python3", "/lab/producer.py"],
|
|
3456
|
+
volumeMounts: [{
|
|
3457
|
+
name: "script",
|
|
3458
|
+
mountPath: "/lab"
|
|
3459
|
+
}],
|
|
3460
|
+
env: [
|
|
3461
|
+
...Object.entries(env).map(([name, value]) => ({
|
|
3462
|
+
name,
|
|
3463
|
+
value
|
|
3464
|
+
})),
|
|
3465
|
+
{
|
|
3466
|
+
name: "POD_NAME",
|
|
3467
|
+
valueFrom: { fieldRef: { fieldPath: "metadata.name" } }
|
|
3468
|
+
},
|
|
3469
|
+
{
|
|
3470
|
+
name: "NODE_NAME",
|
|
3471
|
+
valueFrom: { fieldRef: { fieldPath: "spec.nodeName" } }
|
|
3472
|
+
}
|
|
3473
|
+
]
|
|
3474
|
+
}]
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}];
|
|
3479
|
+
};
|
|
3480
|
+
/** This function gives the pods of the brokers, in the sequence of their names. */
|
|
3481
|
+
const brokersOf = async (cluster) => {
|
|
3482
|
+
return JSON.parse(await cluster.kubectl([
|
|
3483
|
+
"-n",
|
|
3484
|
+
PULSAR_NAMESPACE,
|
|
3485
|
+
"get",
|
|
3486
|
+
"pods",
|
|
3487
|
+
"-l",
|
|
3488
|
+
"component=broker",
|
|
3489
|
+
"-o",
|
|
3490
|
+
"json"
|
|
3491
|
+
])).items.filter((pod) => pod.metadata.deletionTimestamp === void 0).map((pod) => ({
|
|
3492
|
+
pod: pod.metadata.name,
|
|
3493
|
+
node: pod.spec.nodeName ?? "",
|
|
3494
|
+
ip: pod.status?.podIP ?? "",
|
|
3495
|
+
ready: (pod.status?.containerStatuses ?? []).length > 0 && pod.status.containerStatuses.every((status) => status.ready)
|
|
3496
|
+
})).sort((a, b) => a.pod.localeCompare(b.pod));
|
|
3497
|
+
};
|
|
3498
|
+
/** 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. */
|
|
3499
|
+
const brokerOfHost = (brokers, host) => brokers.find((broker) => host.split(".")[0] === broker.pod || host === broker.ip);
|
|
3500
|
+
/**
|
|
3501
|
+
* This function tells why the producers are not at rest, or gives the host of the owner of the
|
|
3502
|
+
* topic. At rest, each of `clients` producers had an answer in the last second and no failure in
|
|
3503
|
+
* the last 2 s. The last lookup of each one is in the last 2 s, and each names the same owner.
|
|
3504
|
+
*/
|
|
3505
|
+
const ownerAtRest = (state, clients) => {
|
|
3506
|
+
if (state.sends.size !== clients) return `${state.sends.size} producers send, not ${clients}`;
|
|
3507
|
+
for (const [pod, send] of state.sends) {
|
|
3508
|
+
if (state.last - send.ok > 1e3) return `the producer ${pod} has no answer in the last second`;
|
|
3509
|
+
if (state.last - send.failed < 2e3) return `the producer ${pod} failed in the last 2 s`;
|
|
3510
|
+
}
|
|
3511
|
+
const owners = /* @__PURE__ */ new Set();
|
|
3512
|
+
for (const pod of state.sends.keys()) {
|
|
3513
|
+
const seen = state.owners.get(pod);
|
|
3514
|
+
if (seen === void 0 || seen.owner === null || state.last - seen.t > 2e3) return `the producer ${pod} has no lookup with an owner in the last 2 s`;
|
|
3515
|
+
owners.add(seen.owner);
|
|
3516
|
+
}
|
|
3517
|
+
if (owners.size !== 1) return `the lookups name ${[...owners].join(" and ")}`;
|
|
3518
|
+
return { owner: [...owners][0] };
|
|
3519
|
+
};
|
|
3520
|
+
const sleep$1 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3521
|
+
/**
|
|
3522
|
+
* This function waits until each broker is ready and the producers are at rest (`ownerAtRest`). It
|
|
3523
|
+
* gives the broker that owns the topic and the host that the lookups name, or why it could not in `within` ms.
|
|
3524
|
+
*/
|
|
3525
|
+
const holdPulsar = async (cluster, state, clients, within = 3e5) => {
|
|
3526
|
+
const end = Date.now() + within;
|
|
3527
|
+
let why = "the hold did not start";
|
|
3528
|
+
do {
|
|
3529
|
+
const brokers = await brokersOf(cluster);
|
|
3530
|
+
const busy = brokers.find((broker) => !broker.ready);
|
|
3531
|
+
const rest = ownerAtRest(state(), clients);
|
|
3532
|
+
if (brokers.length !== 2) why = `${brokers.length} brokers, not 2`;
|
|
3533
|
+
else if (busy !== void 0) why = `the broker ${busy.pod} is not ready`;
|
|
3534
|
+
else if (typeof rest === "string") why = rest;
|
|
3535
|
+
else {
|
|
3536
|
+
const broker = brokerOfHost(brokers, rest.owner);
|
|
3537
|
+
if (broker !== void 0) return {
|
|
3538
|
+
broker,
|
|
3539
|
+
host: rest.owner
|
|
3540
|
+
};
|
|
3541
|
+
why = `the owner ${rest.owner} is no broker`;
|
|
3542
|
+
}
|
|
3543
|
+
await sleep$1(500);
|
|
3544
|
+
} while (Date.now() < end);
|
|
3545
|
+
return why;
|
|
3546
|
+
};
|
|
3547
|
+
/**
|
|
3548
|
+
* This function installs Pulsar in a cluster, with its image on each node. A failed install gives
|
|
3549
|
+
* the pods of Pulsar and the tail of their logs.
|
|
3550
|
+
*/
|
|
3551
|
+
const installPulsar = async (cluster, image) => {
|
|
3552
|
+
try {
|
|
3553
|
+
await helmInstall(cluster, "pulsar", PULSAR_CHART, PULSAR_NAMESPACE, pulsarValues(image), { createNamespace: true });
|
|
3554
|
+
} catch (error) {
|
|
3555
|
+
const state = async (args) => (await cluster.kubectl([
|
|
3556
|
+
"-n",
|
|
3557
|
+
PULSAR_NAMESPACE,
|
|
3558
|
+
...args
|
|
3559
|
+
]).catch((why) => String(why))).slice(-4e3);
|
|
3560
|
+
const pods = await state([
|
|
3561
|
+
"get",
|
|
3562
|
+
"pods",
|
|
3563
|
+
"-o",
|
|
3564
|
+
"wide"
|
|
3565
|
+
]);
|
|
3566
|
+
const logs = await state([
|
|
3567
|
+
"logs",
|
|
3568
|
+
"-l",
|
|
3569
|
+
"app=pulsar",
|
|
3570
|
+
"--tail=30",
|
|
3571
|
+
"--prefix",
|
|
3572
|
+
"--all-containers"
|
|
3573
|
+
]);
|
|
3574
|
+
throw new Error(`the install of Pulsar failed: ${String(error)}\nthe pods of Pulsar:\n${pods}\nthe logs:\n${logs}`);
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
/** These words mark the lines of a broker that the record keeps: the sessions, the leader, the ownership and the topics. */
|
|
3578
|
+
const 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)/;
|
|
3579
|
+
/**
|
|
3580
|
+
* This function gives the record of Pulsar after a run. It has the brokers, and the kept lines of
|
|
3581
|
+
* each broker and of its last container, with the times of the kubelet.
|
|
3582
|
+
*/
|
|
3583
|
+
const pulsarRecord = async (cluster) => {
|
|
3584
|
+
const brokers = await brokersOf(cluster);
|
|
3585
|
+
const logs = {};
|
|
3586
|
+
for (const broker of brokers) for (const previous of [false, true]) {
|
|
3587
|
+
const text = await cluster.kubectl([
|
|
3588
|
+
"-n",
|
|
3589
|
+
PULSAR_NAMESPACE,
|
|
3590
|
+
"logs",
|
|
3591
|
+
broker.pod,
|
|
3592
|
+
"-c",
|
|
3593
|
+
"pulsar-broker",
|
|
3594
|
+
"--since=300s",
|
|
3595
|
+
"--tail=6000",
|
|
3596
|
+
"--timestamps",
|
|
3597
|
+
...previous ? ["--previous"] : []
|
|
3598
|
+
]).catch((error) => String(error));
|
|
3599
|
+
logs[`${broker.pod}${previous ? ", the last container" : ""}`] = text.split("\n").filter((line) => KEPT.test(line)).slice(-80).join("\n").slice(-8e3);
|
|
3600
|
+
}
|
|
3601
|
+
return {
|
|
3602
|
+
brokers,
|
|
3603
|
+
logs
|
|
3604
|
+
};
|
|
3605
|
+
};
|
|
3606
|
+
//#endregion
|
|
3044
3607
|
//#region src/lds.ts
|
|
3045
3608
|
/**
|
|
3046
3609
|
* This filter of RE2 selects the two counters of the listener manager of Envoy that an update of LDS
|
|
@@ -4187,9 +4750,11 @@ const hasRedis = (cell) => cell.declaration !== null && cell.declaration.redis !
|
|
|
4187
4750
|
* This function gives the key of the cluster of a cell: its kube-proxy mode, its node count, and the
|
|
4188
4751
|
* flavor of its mesh. A zone loss has clusters of its own, with a zone on each node.
|
|
4189
4752
|
*/
|
|
4190
|
-
const clusterKey = (cell) => (cell.mesh !== void 0 ? `${cell.mode}-${cell.workers}-${cell.mesh.flavor}` : zoned(cell) ? `${cell.mode}-${cell.workers}-zones` : `${cell.mode}-${cell.workers}`) + (cell.vip === void 0 ? "" : cell.vip.announcer
|
|
4191
|
-
/** This function
|
|
4192
|
-
const
|
|
4753
|
+
const clusterKey = (cell) => (cell.mesh !== void 0 ? `${cell.mode}-${cell.workers}-${cell.mesh.flavor}` : zoned(cell) ? `${cell.mode}-${cell.workers}-zones` : `${cell.mode}-${cell.workers}`) + (cell.vip === void 0 ? "" : cell.vip.announcer !== "metallb" ? "-vip" : cell.vip.memberlist === void 0 ? "-vip-mlb" : `-mlb${valueIn(cell.vip.memberlist.probeInterval, "ms")}x${cell.vip.memberlist.suspicionMult}`) + (cell.pulsar === void 0 ? "" : "-pul");
|
|
4754
|
+
/** 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. */
|
|
4755
|
+
const clusterNameOf = (cell, env) => `lab-${clusterKey(cell)}-${env}`;
|
|
4756
|
+
/** 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. */
|
|
4757
|
+
const zoned = (cell) => cell.trigger.kind === "zone-loss" || cell.vip?.memberlist !== void 0;
|
|
4193
4758
|
/**
|
|
4194
4759
|
* This tracker keeps the latest state of the cell from the lines: each pod, each slice and each
|
|
4195
4760
|
* client. It also has, for each node, the backends of its rules and the hosts of its client sidecar.
|
|
@@ -4202,7 +4767,13 @@ var Tracker = class {
|
|
|
4202
4767
|
clients = /* @__PURE__ */ new Map();
|
|
4203
4768
|
/** This map is the backend of the last answer of each client, by client. */
|
|
4204
4769
|
answers = /* @__PURE__ */ new Map();
|
|
4770
|
+
/** This map is the last send of each producer of a broker that got an answer, and the last that failed. */
|
|
4771
|
+
sends = /* @__PURE__ */ new Map();
|
|
4772
|
+
/** This map is the last lookup of the owner of the topic of each producer, and the host that it names. */
|
|
4773
|
+
owners = /* @__PURE__ */ new Map();
|
|
4205
4774
|
last = 0;
|
|
4775
|
+
/** This time is the last line of a producer. */
|
|
4776
|
+
lastSend = 0;
|
|
4206
4777
|
see = (line) => {
|
|
4207
4778
|
if (typeof line.t === "number" && line.t > this.last && line.ev === "req") this.last = line.t;
|
|
4208
4779
|
if (line.ev === "pod") {
|
|
@@ -4225,6 +4796,23 @@ var Tracker = class {
|
|
|
4225
4796
|
else client.failed = Math.max(client.failed, request.t);
|
|
4226
4797
|
this.clients.set(request.p, client);
|
|
4227
4798
|
if (request.o === "ok" && request.b !== void 0) this.answers.set(request.p, request.b);
|
|
4799
|
+
} else if (line.ev === "send") {
|
|
4800
|
+
const pod = String(line["p"]);
|
|
4801
|
+
const send = this.sends.get(pod) ?? {
|
|
4802
|
+
ok: 0,
|
|
4803
|
+
failed: 0
|
|
4804
|
+
};
|
|
4805
|
+
if (line["o"] === "ok") send.ok = Math.max(send.ok, line.t);
|
|
4806
|
+
else send.failed = Math.max(send.failed, line.t);
|
|
4807
|
+
this.sends.set(pod, send);
|
|
4808
|
+
this.lastSend = Math.max(this.lastSend, line.t);
|
|
4809
|
+
} else if (line.ev === "lookup") {
|
|
4810
|
+
const pod = String(line["p"]);
|
|
4811
|
+
const owner = line["o"] === "ok" && typeof line["owner"] === "string" ? line["owner"] : null;
|
|
4812
|
+
if (line.t >= (this.owners.get(pod)?.t ?? 0)) this.owners.set(pod, {
|
|
4813
|
+
t: line.t,
|
|
4814
|
+
owner
|
|
4815
|
+
});
|
|
4228
4816
|
}
|
|
4229
4817
|
};
|
|
4230
4818
|
servers() {
|
|
@@ -4405,6 +4993,18 @@ const deploy = async (lab, cluster, cell, namespace, images, vip) => {
|
|
|
4405
4993
|
`daemonset/${CLIENT}`,
|
|
4406
4994
|
"--timeout=300s"
|
|
4407
4995
|
]);
|
|
4996
|
+
if (cell.pulsar !== void 0) {
|
|
4997
|
+
if (images.pulsar === void 0) throw new Error(`${cell.name}: the cluster has no image of Pulsar`);
|
|
4998
|
+
await cluster.apply(pulsarObjects(cell, namespace, images.pulsar));
|
|
4999
|
+
await cluster.kubectl([
|
|
5000
|
+
"-n",
|
|
5001
|
+
namespace,
|
|
5002
|
+
"rollout",
|
|
5003
|
+
"status",
|
|
5004
|
+
`deployment/${PRODUCER}`,
|
|
5005
|
+
"--timeout=300s"
|
|
5006
|
+
]);
|
|
5007
|
+
}
|
|
4408
5008
|
if (cell.vip !== void 0) {
|
|
4409
5009
|
if (vip === null) throw new Error(`${cell.name}: the cluster has no VIP`);
|
|
4410
5010
|
const served = await vipServed(cluster, namespace, "lab", vip);
|
|
@@ -4488,7 +5088,7 @@ const probeThePods = async (ctx) => {
|
|
|
4488
5088
|
if (key === ctx.probed) return;
|
|
4489
5089
|
const netns = [];
|
|
4490
5090
|
for (const pod of pods) {
|
|
4491
|
-
const container = pod.labels["app"] === "lab" ? "app" : pod.labels["app"] === "client" ? CLIENT : REDIS;
|
|
5091
|
+
const container = pod.labels["app"] === "lab" ? "app" : pod.labels["app"] === "client" ? CLIENT : pod.labels["app"] === "producer" ? PRODUCER : REDIS;
|
|
4492
5092
|
const pid = await ctx.lab.pidOf(pod.node, ctx.namespace, pod.name, container);
|
|
4493
5093
|
netns.push(await ctx.lab.netnsOf(pod.node, pid));
|
|
4494
5094
|
}
|
|
@@ -4671,6 +5271,35 @@ const doStep = async (ctx, step, replicas, background) => {
|
|
|
4671
5271
|
pid: await lab.pidOf(target.node, namespace, target.pod, "app"),
|
|
4672
5272
|
pod: null
|
|
4673
5273
|
};
|
|
5274
|
+
if (which === "speaker") {
|
|
5275
|
+
const owner = lostNodeOf(cluster);
|
|
5276
|
+
const pod = JSON.parse(await cluster.kubectl([
|
|
5277
|
+
"-n",
|
|
5278
|
+
METALLB_NAMESPACE,
|
|
5279
|
+
"get",
|
|
5280
|
+
"pods",
|
|
5281
|
+
"-l",
|
|
5282
|
+
"app.kubernetes.io/component=speaker",
|
|
5283
|
+
"--field-selector",
|
|
5284
|
+
`spec.nodeName=${owner}`,
|
|
5285
|
+
"-o",
|
|
5286
|
+
"json"
|
|
5287
|
+
])).items[0];
|
|
5288
|
+
if (pod === void 0) throw new Error(`no speaker of MetalLB is on ${owner}`);
|
|
5289
|
+
return {
|
|
5290
|
+
node: owner,
|
|
5291
|
+
pid: await lab.pidOf(owner, METALLB_NAMESPACE, pod.metadata.name, "speaker"),
|
|
5292
|
+
pod: null
|
|
5293
|
+
};
|
|
5294
|
+
}
|
|
5295
|
+
if (which === "broker") {
|
|
5296
|
+
if (ctx.broker === null) throw new Error(`${ctx.cell.name}: the run has no broker that owns the topic`);
|
|
5297
|
+
return {
|
|
5298
|
+
node: ctx.broker.node,
|
|
5299
|
+
pid: await lab.pidOf(ctx.broker.node, PULSAR_NAMESPACE, ctx.broker.pod, "pulsar-broker"),
|
|
5300
|
+
pod: null
|
|
5301
|
+
};
|
|
5302
|
+
}
|
|
4674
5303
|
if (which === "redis") {
|
|
4675
5304
|
const redis = [...ctx.tracker.pods.values()].find((pod) => pod.labels["app"] === "redis" && !pod.del);
|
|
4676
5305
|
return {
|
|
@@ -4719,7 +5348,8 @@ const doStep = async (ctx, step, replicas, background) => {
|
|
|
4719
5348
|
}
|
|
4720
5349
|
case "signal": {
|
|
4721
5350
|
if (step.target === "clients") return signalClients(ctx, step.signal);
|
|
4722
|
-
const at = await where(step.target);
|
|
5351
|
+
const at = (step.target === "speaker" && step.signal === "SIGCONT" ? ctx.speaker : void 0) ?? await where(step.target);
|
|
5352
|
+
if (step.target === "speaker") ctx.speaker = step.signal === "SIGSTOP" ? at : void 0;
|
|
4723
5353
|
return lab.act(at.node, {
|
|
4724
5354
|
op: "signal",
|
|
4725
5355
|
pid: at.pid,
|
|
@@ -4878,6 +5508,19 @@ const recordVip = async (ctx) => {
|
|
|
4878
5508
|
macs
|
|
4879
5509
|
});
|
|
4880
5510
|
};
|
|
5511
|
+
/** This function records the brokers after a run, with the kept lines of each broker. It stamps the wall clock and the monotonic clock. */
|
|
5512
|
+
const recordPulsar = async (ctx) => {
|
|
5513
|
+
const wall = Date.now();
|
|
5514
|
+
const t = Number(process.hrtime.bigint()) / 1e6;
|
|
5515
|
+
ctx.lab.record({
|
|
5516
|
+
ev: "pulsar-record",
|
|
5517
|
+
t,
|
|
5518
|
+
wall,
|
|
5519
|
+
version: PULSAR_VERSION,
|
|
5520
|
+
owner: ctx.broker?.pod ?? null,
|
|
5521
|
+
...await pulsarRecord(ctx.cluster)
|
|
5522
|
+
});
|
|
5523
|
+
};
|
|
4881
5524
|
/** This function puts the replicas of a node first, and keeps the sequence of the others. */
|
|
4882
5525
|
const lostFirst = (replicas, node) => [...replicas.filter((replica) => replica.node === node), ...replicas.filter((replica) => replica.node !== node)];
|
|
4883
5526
|
/** This function tells if a run is over: the trigger is done, and the cell is at rest again. */
|
|
@@ -4957,10 +5600,25 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
|
|
|
4957
5600
|
await sleep(valueIn(announcer.settle, "ms") + valueIn(cell.workload.timeout, "ms") + 1e3);
|
|
4958
5601
|
}
|
|
4959
5602
|
if (await steady(ctx, 3e5) !== null) return empty("the cell was not at rest after the wait for a quiet VM", await lab.now());
|
|
5603
|
+
ctx.broker = null;
|
|
5604
|
+
if (cell.pulsar !== void 0) {
|
|
5605
|
+
const tracker = ctx.tracker;
|
|
5606
|
+
const held = await holdPulsar(cluster, () => ({
|
|
5607
|
+
owners: tracker.owners,
|
|
5608
|
+
sends: tracker.sends,
|
|
5609
|
+
last: tracker.lastSend
|
|
5610
|
+
}), cell.pulsar.clients);
|
|
5611
|
+
if (typeof held === "string") return empty(`the producers were not at rest: ${held}`, await lab.now());
|
|
5612
|
+
ctx.broker = {
|
|
5613
|
+
pod: held.broker.pod,
|
|
5614
|
+
node: held.broker.node,
|
|
5615
|
+
host: held.host
|
|
5616
|
+
};
|
|
5617
|
+
}
|
|
4960
5618
|
const replicas = zoneLoss ? lostFirst(replicasOf(ctx), lostNodeOf(cluster)) : replicasOf(ctx);
|
|
4961
5619
|
if (zoneLoss && cell.vip === void 0 && replicas[0]?.node !== lostNodeOf(cluster)) return empty(`no replica is on the node of the lost zone, ${lostNodeOf(cluster)}`, await lab.now());
|
|
4962
5620
|
const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));
|
|
4963
|
-
const target = cell.vip !== void 0 || hasRedis(cell) && [
|
|
5621
|
+
const target = cell.vip !== void 0 || cell.pulsar !== void 0 || hasRedis(cell) && [
|
|
4964
5622
|
"rst",
|
|
4965
5623
|
"blackhole",
|
|
4966
5624
|
"freeze-process",
|
|
@@ -4968,7 +5626,7 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
|
|
|
4968
5626
|
"rollout",
|
|
4969
5627
|
"latency"
|
|
4970
5628
|
].includes(cell.trigger.kind) ? null : replicas[0];
|
|
4971
|
-
const paused = cell.template === "T07" && cell.trigger.kind === "freeze-process" ? cluster.nodes[1] ?? cluster.nodes[0] : zoneLoss ? lostNodeOf(cluster) : null;
|
|
5629
|
+
const paused = cell.template === "T07" && cell.trigger.kind === "freeze-process" && cell.vip === void 0 ? cluster.nodes[1] ?? cluster.nodes[0] : zoneLoss ? lostNodeOf(cluster) : null;
|
|
4972
5630
|
const cleared = await lab.forgetHandshakes(ctx.clusterIp, 80);
|
|
4973
5631
|
lab.take();
|
|
4974
5632
|
for (const key of Object.keys(lab.dropped)) delete lab.dropped[key];
|
|
@@ -5061,6 +5719,7 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
|
|
|
5061
5719
|
const end = await lab.now();
|
|
5062
5720
|
const timedOut = Date.now() >= limit;
|
|
5063
5721
|
if (cell.vip !== void 0) await recordVip(ctx).catch((error) => notes.push(`the record of the VIP failed: ${String(error)}`));
|
|
5722
|
+
if (cell.pulsar !== void 0) await recordPulsar(ctx).catch((error) => notes.push(`the record of Pulsar failed: ${String(error)}`));
|
|
5064
5723
|
for (const step of plan.restore) try {
|
|
5065
5724
|
await doStep(ctx, step, replicas, background);
|
|
5066
5725
|
} catch (error) {
|
|
@@ -5103,6 +5762,7 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
|
|
|
5103
5762
|
},
|
|
5104
5763
|
target: stopped,
|
|
5105
5764
|
paused,
|
|
5765
|
+
...ctx.broker === null ? {} : { broker: ctx.broker.host },
|
|
5106
5766
|
replicas,
|
|
5107
5767
|
void: null,
|
|
5108
5768
|
lines,
|
|
@@ -5147,7 +5807,8 @@ const writeResult = (out, result) => {
|
|
|
5147
5807
|
voided: verdict.voided,
|
|
5148
5808
|
failed: verdict.failed,
|
|
5149
5809
|
median: verdict.median,
|
|
5150
|
-
why: verdict.why
|
|
5810
|
+
why: verdict.why,
|
|
5811
|
+
substrate: substrateKey(substrateOf(result.manifests[0] ?? {}, result.at, "E2"))
|
|
5151
5812
|
})}\n`, { flag: "a" });
|
|
5152
5813
|
};
|
|
5153
5814
|
/**
|
|
@@ -5185,19 +5846,21 @@ const runKindCells = async (cells, options) => {
|
|
|
5185
5846
|
for (const [name, ref] of Object.entries(announcer.images)) pinned[name] = await localPin(ref);
|
|
5186
5847
|
announcerImages.set(announcer.name, pinned);
|
|
5187
5848
|
}
|
|
5849
|
+
const pulsarImage = cells.some((cell) => cell.pulsar !== void 0) ? await localPin(PULSAR_IMAGE) : void 0;
|
|
5188
5850
|
const images = {
|
|
5189
5851
|
lab: image,
|
|
5190
5852
|
redis: redisImage,
|
|
5191
5853
|
grpc: grpcImage,
|
|
5192
|
-
mesh: meshImages
|
|
5854
|
+
mesh: meshImages,
|
|
5855
|
+
pulsar: pulsarImage
|
|
5193
5856
|
};
|
|
5194
5857
|
const groups = /* @__PURE__ */ new Map();
|
|
5195
5858
|
for (const cell of cells) groups.set(clusterKey(cell), [...groups.get(clusterKey(cell)) ?? [], cell]);
|
|
5196
5859
|
const results = [];
|
|
5197
|
-
for (const
|
|
5860
|
+
for (const group of groups.values()) {
|
|
5198
5861
|
const partial = /* @__PURE__ */ new Map();
|
|
5199
5862
|
for (let env = 0; env < environments; env += 1) {
|
|
5200
|
-
const s = scenario(
|
|
5863
|
+
const s = scenario(clusterNameOf(group[0], env), group[0].mode, group[0].workers, void 0, zoned(group[0]));
|
|
5201
5864
|
const dir = join(options.work ?? join(tmpdir(), "systems-kind-work"), s.name);
|
|
5202
5865
|
const mesh = group[0].mesh;
|
|
5203
5866
|
options.log(`cluster ${s.name}: ${s.dataplane}, ${s.workers} workers${mesh === void 0 ? "" : `, Istio ${mesh.flavor}`}`);
|
|
@@ -5238,9 +5901,16 @@ const runKindCells = async (cells, options) => {
|
|
|
5238
5901
|
for (const each of Object.values(pinned)) await cluster.load(each);
|
|
5239
5902
|
const began = Date.now();
|
|
5240
5903
|
vip = announcer.vipOf((await vipNetworkOf(cluster.network)).subnet, lostNodeOf(cluster), workersOf(cluster));
|
|
5241
|
-
await announcer.install(cluster, pinned, lostNodeOf(cluster), workersOf(cluster), vip);
|
|
5904
|
+
await announcer.install(cluster, pinned, lostNodeOf(cluster), workersOf(cluster), vip, group[0].vip);
|
|
5242
5905
|
options.log(` ${announcer.name} installed in ${Math.round((Date.now() - began) / 100) / 10} s, the VIP ${vip}`);
|
|
5243
5906
|
}
|
|
5907
|
+
if (group[0].pulsar !== void 0) {
|
|
5908
|
+
if (pulsarImage === void 0) throw new Error(`${group[0].name}: the run has no image of Pulsar`);
|
|
5909
|
+
await cluster.load(pulsarImage);
|
|
5910
|
+
const began = Date.now();
|
|
5911
|
+
await installPulsar(cluster, pulsarImage);
|
|
5912
|
+
options.log(` Pulsar ${PULSAR_VERSION} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);
|
|
5913
|
+
}
|
|
5244
5914
|
lab = await openKindLab(cluster, image);
|
|
5245
5915
|
const { GenericContainer } = await import("testcontainers");
|
|
5246
5916
|
const Shared = class extends GenericContainer {
|
|
@@ -5279,7 +5949,8 @@ const runKindCells = async (cells, options) => {
|
|
|
5279
5949
|
probed: "",
|
|
5280
5950
|
listened: "",
|
|
5281
5951
|
expected: cell.pod.replicas,
|
|
5282
|
-
pids: /* @__PURE__ */ new Map()
|
|
5952
|
+
pids: /* @__PURE__ */ new Map(),
|
|
5953
|
+
broker: null
|
|
5283
5954
|
};
|
|
5284
5955
|
const share = Math.ceil(cell.runs / environments) - (env < cell.runs % environments || cell.runs % environments === 0 ? 0 : 1);
|
|
5285
5956
|
const runs = [await runOnce(ctx, env, -1, true, ours)];
|
|
@@ -5294,13 +5965,15 @@ const runKindCells = async (cells, options) => {
|
|
|
5294
5965
|
env,
|
|
5295
5966
|
scenario: s,
|
|
5296
5967
|
cluster: manifest,
|
|
5968
|
+
substrate: substrateOn("E2"),
|
|
5297
5969
|
images: {
|
|
5298
5970
|
lab: image,
|
|
5299
5971
|
redis: redisImage,
|
|
5300
5972
|
...stackOf(cell) === "grpc" ? { grpc: grpcImage } : {},
|
|
5301
5973
|
...cell.mesh === void 0 ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]])),
|
|
5302
5974
|
...s.cni === "cilium" ? Object.fromEntries(Object.entries(CILIUM_IMAGES).map(([name, ref]) => [`cilium-${name}`, ref])) : {},
|
|
5303
|
-
...cell.vip === void 0 ? {} : announcerOf(cell).images
|
|
5975
|
+
...cell.vip === void 0 ? {} : announcerOf(cell).images,
|
|
5976
|
+
...cell.pulsar === void 0 ? {} : { pulsar: PULSAR_IMAGE }
|
|
5304
5977
|
}
|
|
5305
5978
|
});
|
|
5306
5979
|
entry.loads.push(...loads);
|
|
@@ -5368,14 +6041,17 @@ const estimate = (cell, environments = 2) => {
|
|
|
5368
6041
|
"ztunnel-restart": 40,
|
|
5369
6042
|
"certificate-expiry": hold + 20,
|
|
5370
6043
|
"cannot-serve": hold + 20,
|
|
5371
|
-
"zone-loss": hold + 45
|
|
6044
|
+
"zone-loss": hold + 45,
|
|
6045
|
+
...cell.vip === void 0 ? {} : { "freeze-process": hold + 20 }
|
|
5372
6046
|
};
|
|
5373
6047
|
const after = observationAfter(cell);
|
|
5374
6048
|
const observed = after === null ? 0 : hold + valueIn(after, "s");
|
|
5375
6049
|
const closes = cell.prediction.kind === "ends" && cell.prediction.band !== null ? valueIn(cell.prediction.band.hi, "s") + 3 : 0;
|
|
5376
6050
|
const quiet = cell.trigger.quiet === true ? hold + 10 : 0;
|
|
5377
|
-
const
|
|
5378
|
-
|
|
6051
|
+
const move = cell.vip === void 0 ? 0 : 30;
|
|
6052
|
+
const broker = cell.pulsar === void 0 ? 0 : 150;
|
|
6053
|
+
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")));
|
|
6054
|
+
return ((cell.mesh === void 0 ? 60 : 180) + (cell.vip === void 0 ? 0 : 60) + (cell.pulsar === void 0 ? 0 : 240)) * environments + (cell.runs + environments) * each;
|
|
5379
6055
|
};
|
|
5380
6056
|
/**
|
|
5381
6057
|
* This function gives an estimate of the time of a cell: its setup, then each run and each
|
|
@@ -5406,6 +6082,6 @@ const shardOf = (cells, index, count) => {
|
|
|
5406
6082
|
return out;
|
|
5407
6083
|
};
|
|
5408
6084
|
//#endregion
|
|
5409
|
-
export { ACCESS_LOG_FORMAT, ANNOUNCE_ANNOTATION, CHANNEL_USE, CILIUM_CHART, CILIUM_CONFIG_KEYS, CILIUM_IMAGES, CILIUM_VERSION, CILIUM_WORKLOADS, CLIENT, CLIENT_ACCOUNT, CLOSE_LEAD, CONTROLLER, DATAPLANES, DNS_PORT, DOCUMENTED, DRAIN_RULES, EDS_EVERY, FLAG_CHECK, FORMAT, FORMAT_V1, HBONE_PORT, HEALTH_PORT, HELM, ISTIOCTL, ISTIO_IMAGES, ISTIO_VERSION, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, KUBE_VIP, KUBE_VIP_ANNOUNCER, KUBE_VIP_ARP_RATE, KUBE_VIP_ELECTION, KUBE_VIP_IMAGE, KUBE_VIP_LEASE, KUBE_VIP_VERSION, LAST_SLACK, LB_CLASS, LDS_EVERY, LDS_FILTER, LOG_LABEL, MEMBERLIST, MESH_IMAGES, MESH_WORKLOADS, METALLB_ANNOTATION, METALLB_ANNOUNCER, METALLB_CHART, METALLB_IMAGES, METALLB_NAMESPACE, METALLB_POOL, METALLB_SPAM, METALLB_VERSION, MIDDLEBOX, NETEM_DRAIN, NETNS_DEFAULTS, NODE_IMAGES, NODE_LIFECYCLE, O04, O09, O10, O16, OUTSIDE, POD_LINK, POLICY, PORT, QUIET_LEAD, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, SPREAD_LEAD, STAGES, SYSCTL, TCP_STATE, VIP_ANNOTATION, VIP_LABEL, WATCHDOG, ZONE_KEPT, ZONE_LABEL, ZONE_LOST, ZTUNNEL_PODS, announcementsOf, announcerOf, arpMacOf, backendsAt, backendsOf, backlogEnd, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, certificateArgs, checkRun, cidrOverlap, cidrRange, ciliumBackendsOf, ciliumMode, ciliumValues, clientObjects, clusterFilter, compose, createCluster, cyclesOf, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dnsCommands, drainBand, dropOf, emptyOf, ensureChart, ensurePinnedChart, ensureTool, estimatedTime, factsOf, failuresOf, faultSpan, followsZtunnel, goDuration, helmInstall, holdMetallb, holdVip, hostsOf, installCilium, installKubeVip, installMesh, installMetallb, intakeAfter, intakeOf, isKubeProxyMode, isTunnelState, istioOperator, judge, judgeKind, keptKernel, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeVipObjects, kubeVipPods, kubeVipReady, kubeletBand, labImage, labSource, latchBand, leafCertificates, learnWindow, lintCell, listenerCountsOf, localPin, macsAt, medianInterval, medianText, meshKey, metallbObjects, metallbRecord, metallbValues, metallbVipOf, middle, middleboxCommands, namesService, namespaceObject, namespaceOf, nodeLossBand, nodeNames, nodeReturnBand, observationAfter, observationEnd, openKindLab, outboundCluster, outcomesOf, overshootBand, ownerOrder, pausedWindow, periodOf, placementOf, planOf, platformKey, podsAt, podsOf, policyObject, pollsListeners, predictionText, probeOf, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestBand, requestsOf, restartBand, restartOf, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverCluster, serverObjects, sessionsAt, setZtunnelEnv, sha256, shardOf, sharedGate, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, sliceReadyAt, sliceReadyOf, slicesOf, socketsOf, speakersReady, stackOf, stageLabImage, stallRemovalBand, stopOf, stoppingOf, suspicionOf, sysctlCommand, tarEntry, timeSpread, toolPath, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, vipLeaseOf, vipNetworkOf, vipOf, vipServed, vipService, writeResult, zoneOf, ztunnelCertificates, ztunnelEnvArgs, ztunnelPodsOf, ztunnelReplaced };
|
|
6085
|
+
export { ACCESS_LOG_FORMAT, ANNOUNCE_ANNOTATION, CHANNEL_USE, CILIUM_CHART, CILIUM_CONFIG_KEYS, CILIUM_IMAGES, CILIUM_VERSION, CILIUM_WORKLOADS, CLIENT, CLIENT_ACCOUNT, CLOSE_LEAD, CONTROLLER, DATAPLANES, DNS_PORT, DOCUMENTED, DRAIN_RULES, EDS_EVERY, FLAG_CHECK, FORMAT, FORMAT_V1, HBONE_PORT, HEALTH_PORT, HELM, ISTIOCTL, ISTIO_IMAGES, ISTIO_VERSION, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, KUBE_VIP, KUBE_VIP_ANNOUNCER, KUBE_VIP_ARP_RATE, KUBE_VIP_ELECTION, KUBE_VIP_IMAGE, KUBE_VIP_LEASE, KUBE_VIP_VERSION, LAST_SLACK, LB_CLASS, LDS_EVERY, LDS_FILTER, LOG_LABEL, MEMBERLIST, MEMBERLIST_FILE, MESH_IMAGES, MESH_WORKLOADS, METALLB_ANNOTATION, METALLB_ANNOUNCER, METALLB_CHART, METALLB_IMAGES, METALLB_NAMESPACE, METALLB_POOL, METALLB_SPAM, METALLB_VERSION, MIDDLEBOX, NETEM_DRAIN, NETNS_DEFAULTS, NODE_IMAGES, NODE_LIFECYCLE, O04, O09, O10, O16, OUTSIDE, PING, POD_LINK, POLICY, PORT, PRODUCER, PRODUCER_BACKOFF, PRODUCER_SCRIPT, PULSAR_CHART, PULSAR_IMAGE, PULSAR_LOOKUP, PULSAR_NAMESPACE, PULSAR_SERVICE, PULSAR_TOPIC, PULSAR_VERSION, QUIET_LEAD, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, SESSION, SPREAD_LEAD, STAGES, SYSCTL, TCP_STATE, TICK, VIP_ANNOTATION, VIP_LABEL, WATCHDOG, ZONE_KEPT, ZONE_LABEL, ZONE_LOST, ZTUNNEL_PODS, announcementsOf, announcerOf, arpMacOf, backendsAt, backendsOf, backlogEnd, bandOf, brokerOfHost, brokersOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, certificateArgs, checkRun, cidrOverlap, cidrRange, ciliumBackendsOf, ciliumMode, ciliumValues, clientObjects, clusterFilter, clusterNameOf, compose, createCluster, cyclesOf, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dnsCommands, drainBand, dropOf, emptyOf, ensureChart, ensurePinnedChart, ensureTool, estimatedTime, factsOf, failuresOf, faultSpan, followsZtunnel, goDuration, helmInstall, holdMetallb, holdPulsar, holdVip, hostsOf, installCilium, installKubeVip, installMesh, installMetallb, installPulsar, intakeAfter, intakeOf, isKubeProxyMode, isTunnelState, istioOperator, judge, judgeKind, judgeReconnects, keptKernel, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeVipObjects, kubeVipPods, kubeVipReady, kubeletBand, labImage, labSource, latchBand, latestReturn, leafCertificates, learnWindow, lintCell, listenerCountsOf, localPin, macsAt, medianInterval, medianText, memberlistFileOf, memberlistTermsOf, meshKey, metallbObjects, metallbRecord, metallbValues, metallbVipOf, middle, middleboxCommands, namesService, namespaceObject, namespaceOf, nodeLossBand, nodeNames, nodeReturnBand, observationAfter, observationEnd, openKindLab, outboundCluster, outcomesOf, overshootBand, ownerAtRest, ownerOrder, pausedWindow, periodOf, placementOf, planOf, platformKey, podsAt, podsOf, policyObject, pollsListeners, predictionText, probeOf, propagationOf, propagationSource, pulsarObjects, pulsarRecord, pulsarValues, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestBand, requestsOf, restartBand, restartOf, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverCluster, serverObjects, sessionsAt, setZtunnelEnv, sha256, shardOf, sharedGate, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, sliceReadyAt, sliceReadyOf, slicesOf, socketsOf, speakersReady, stackOf, stageLabImage, stallRemovalBand, stopOf, stoppingOf, suspicionOf, sysctlCommand, tarEntry, timeSpread, toolPath, transitionOf, triesOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, vipLeaseOf, vipNetworkOf, vipOf, vipServed, vipService, writeResult, zoneOf, ztunnelCertificates, ztunnelEnvArgs, ztunnelPodsOf, ztunnelReplaced };
|
|
5410
6086
|
|
|
5411
6087
|
//# sourceMappingURL=index.js.map
|