@mark1russell7/kind 0.6.0 → 0.8.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 +30 -1
- package/build/index.d.ts +112 -9
- package/build/index.js +453 -33
- package/build/index.js.map +1 -1
- package/build/{judge-DNCI5vvt.js → judge-Cgvkh4JX.js} +111 -10
- package/build/judge-Cgvkh4JX.js.map +1 -0
- package/build/{judge-B9kVpFE-.d.ts → judge-gpC49mVw.d.ts} +106 -6
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/image/client-grpc.mjs +17 -5
- package/package.json +2 -2
- package/src/client-lines.ts +37 -6
- package/build/judge-DNCI5vvt.js.map +0 -1
package/build/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as signalOf, A as medianInterval, B as emptyOf, C as propagationOf, D as unitOf, E as reusesReset, F as backendsAt, G as outcomesOf, H as intakeOf, I as byClients, J as recordsOf, K as podsAt, L as byNode, M as quantileOf, N as HBONE_PORT, O as updatesOf, P as TCP_STATE, Q as rulesOf, R as deletionOf, S as observationEnd, T as referenceOf, U as isTunnelState, V as failuresOf, W as kernelCounts, X as restartOf, Y as requestsOf, Z as restartsAt, _ as checkRun, _t as seconds, a as calibrationOf, at as stoppingOf, b as judgeKind, c as predictionText, ct as ztunnelPodsOf, d as FORMAT_V1, dt as HEALTH_PORT, et as signalsOf, f as judge, ft as LOG_LABEL, g as LAST_SLACK, gt as bandOf, h as KIND_NOISE, ht as SERVER, i as readRecord, it as socketsOf, j as middle, k as RELATIVE, l as timeSpread, lt as ztunnelReplaced, m as upgradeCell, mt as REDIS, n as kindCellRecordSchema, nt as sliceEmptyOf, o as cellsTable, ot as transitionOf, p as upgrade, pt as PORT, q as podsOf, r as kindCellRecordV1Schema, rt as slicesOf, s as medianText, st as tunnelsOf, t as STAGES, tt as sliceDropOf, u as FORMAT, ut as CLIENT, v as deletionsOf, vt as secondsOf, w as propagationSource, x as observationAfter, y as factsOf, yt as showBand, z as dropOf } from "./judge-Cgvkh4JX.js";
|
|
2
2
|
import { valueIn } from "@mark1russell7/quantity";
|
|
3
3
|
import { execFile, execFileSync } from "node:child_process";
|
|
4
4
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -637,6 +637,8 @@ const DELETION_COST = "controller.kubernetes.io/pod-deletion-cost";
|
|
|
637
637
|
const POLICY = "lab-deny";
|
|
638
638
|
/** This service account is the account of the clients: their pods name no other account. */
|
|
639
639
|
const CLIENT_ACCOUNT = "default";
|
|
640
|
+
/** This selector gives the ztunnel pods in istio-system: the label `app` of the chart of ztunnel. */
|
|
641
|
+
const ZTUNNEL_PODS = "app=ztunnel";
|
|
640
642
|
/**
|
|
641
643
|
* This function gives the AuthorizationPolicy of the trigger `authorization-policy`. It denies each
|
|
642
644
|
* request to the server pods from the principal of the service account `account`. Istio gives a
|
|
@@ -656,6 +658,186 @@ const policyObject = (account) => ({
|
|
|
656
658
|
rules: [{ from: [{ source: { principals: [`cluster.local/ns/{namespace}/sa/${account}`] } }] }]
|
|
657
659
|
}
|
|
658
660
|
});
|
|
661
|
+
/** These are the names of the sysctls that the steps of a plan set. Each one is a sysctl of a network namespace. */
|
|
662
|
+
const SYSCTL = { retries: "net.ipv4.tcp_retries2" };
|
|
663
|
+
/**
|
|
664
|
+
* These are the values of those sysctls in a new network namespace of Linux v6.17. A restore puts
|
|
665
|
+
* them back. `tcp_retries2` is TCP_RETR2 (include/net/tcp.h, line 105, and net/ipv4/tcp_ipv4.c,
|
|
666
|
+
* line 3604).
|
|
667
|
+
*/
|
|
668
|
+
const NETNS_DEFAULTS = { retries: "15" };
|
|
669
|
+
/**
|
|
670
|
+
* These are the names of the middlebox in the filter table of a replica. One chain takes the
|
|
671
|
+
* segments from the peers, and one chain takes the segments to them. The table of xt_recent holds
|
|
672
|
+
* the time of the last segment of each peer.
|
|
673
|
+
*/
|
|
674
|
+
const MIDDLEBOX = {
|
|
675
|
+
input: "LAB-MBX-IN",
|
|
676
|
+
output: "LAB-MBX-OUT",
|
|
677
|
+
table: "LAB-MBX"
|
|
678
|
+
};
|
|
679
|
+
/** 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. */
|
|
680
|
+
const QUIET_LEAD = {
|
|
681
|
+
value: 1,
|
|
682
|
+
unit: "s"
|
|
683
|
+
};
|
|
684
|
+
/** 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. */
|
|
685
|
+
const CLOSE_LEAD = {
|
|
686
|
+
value: 2,
|
|
687
|
+
unit: "s"
|
|
688
|
+
};
|
|
689
|
+
/** This time is how long each client calls on its new channel at the fault of a gap with `close`, before it closes it. */
|
|
690
|
+
const CHANNEL_USE = {
|
|
691
|
+
value: 5,
|
|
692
|
+
unit: "s"
|
|
693
|
+
};
|
|
694
|
+
const IPV4 = /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/;
|
|
695
|
+
/**
|
|
696
|
+
* This function gives the command of the injector that sets one sysctl of its network namespace.
|
|
697
|
+
* Only a sysctl of the namespace (`net.`) and a whole number reach it.
|
|
698
|
+
*/
|
|
699
|
+
const sysctlCommand = (key, value) => {
|
|
700
|
+
if (!/^net\.[a-z0-9_.]+$/.test(key)) throw new Error(`not a sysctl of a network namespace: ${key}`);
|
|
701
|
+
if (!/^\d+$/.test(value)) throw new Error(`not a whole number: ${value}`);
|
|
702
|
+
return [
|
|
703
|
+
"sysctl",
|
|
704
|
+
"-w",
|
|
705
|
+
`${key}=${value}`
|
|
706
|
+
];
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* This function gives the commands of the injector that put a stateful middlebox in its network
|
|
710
|
+
* namespace, with each peer, on the local port `port`. With `idle` null, they remove it.
|
|
711
|
+
*
|
|
712
|
+
* The middlebox forgets a flow of the port after `idle` with no segment. Then it drops each later
|
|
713
|
+
* segment of it: the silent-drop form of the router of the harness. Conntrack stays as it is. The
|
|
714
|
+
* local leg from ztunnel to the app has no PING, and istio-cni needs its conntrack entry. The
|
|
715
|
+
* connmark of that entry sends each answer of the app back to ztunnel (cni/pkg/iptables/iptables.go
|
|
716
|
+
* of Istio 1.31.3, lines 172–178, 278–284 and 336–347).
|
|
717
|
+
*
|
|
718
|
+
* The match recent keeps a table in each network namespace (xt_recent.c of Linux v6.17). Its key is
|
|
719
|
+
* the address of the peer: the source of a segment from it, or the destination of a segment to it
|
|
720
|
+
* (lines 251–257). The chain of each direction has these rules, in sequence:
|
|
721
|
+
* - In the input chain only, a SYN sets the time of its peer and passes: a new flow.
|
|
722
|
+
* - A segment of a peer with a time in the last `idle` seconds passes, and it sets the time again
|
|
723
|
+
* (lines 298–321).
|
|
724
|
+
* - A segment of a peer with an older time is dropped, and the time stays old. Thus the flow stays forgotten.
|
|
725
|
+
* - A segment of a peer that the table does not hold sets its time and passes (lines 283–290). That
|
|
726
|
+
* is the flow that was open before the set.
|
|
727
|
+
*
|
|
728
|
+
* Each rule that passes a segment goes back to INPUT or OUTPUT (RETURN), thus the other rules of the
|
|
729
|
+
* namespace still act on it. The key is an address, not a flow. Thus a new SYN of a peer also refreshes an
|
|
730
|
+
* old flow of the peer. A cell of the middlebox opens no new flow in its gap.
|
|
731
|
+
*/
|
|
732
|
+
const middleboxCommands = (peers, idle, port) => {
|
|
733
|
+
if (peers.length === 0) throw new Error("a middlebox needs a peer");
|
|
734
|
+
for (const peer of peers) if (!IPV4.test(peer)) throw new Error(`not an address: ${peer}`);
|
|
735
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`not a port: ${port}`);
|
|
736
|
+
const op = idle === null ? "-D" : "-I";
|
|
737
|
+
const jumps = peers.flatMap((peer) => [[
|
|
738
|
+
"iptables",
|
|
739
|
+
op,
|
|
740
|
+
"INPUT",
|
|
741
|
+
"-s",
|
|
742
|
+
`${peer}/32`,
|
|
743
|
+
"-p",
|
|
744
|
+
"tcp",
|
|
745
|
+
"--dport",
|
|
746
|
+
String(port),
|
|
747
|
+
"-j",
|
|
748
|
+
MIDDLEBOX.input
|
|
749
|
+
], [
|
|
750
|
+
"iptables",
|
|
751
|
+
op,
|
|
752
|
+
"OUTPUT",
|
|
753
|
+
"-d",
|
|
754
|
+
`${peer}/32`,
|
|
755
|
+
"-p",
|
|
756
|
+
"tcp",
|
|
757
|
+
"--sport",
|
|
758
|
+
String(port),
|
|
759
|
+
"-j",
|
|
760
|
+
MIDDLEBOX.output
|
|
761
|
+
]]);
|
|
762
|
+
const chains = [MIDDLEBOX.input, MIDDLEBOX.output];
|
|
763
|
+
if (idle === null) return [
|
|
764
|
+
...chains.map((chain) => [
|
|
765
|
+
"iptables",
|
|
766
|
+
"-F",
|
|
767
|
+
chain
|
|
768
|
+
]),
|
|
769
|
+
...jumps,
|
|
770
|
+
...chains.map((chain) => [
|
|
771
|
+
"iptables",
|
|
772
|
+
"-X",
|
|
773
|
+
chain
|
|
774
|
+
])
|
|
775
|
+
];
|
|
776
|
+
const time = valueIn(idle, "s");
|
|
777
|
+
if (!Number.isInteger(time) || time < 1) throw new Error(`the idle timeout of the middlebox is a whole number of seconds, not ${time}`);
|
|
778
|
+
const rules = (chain, side) => {
|
|
779
|
+
const recent = [
|
|
780
|
+
"-m",
|
|
781
|
+
"recent",
|
|
782
|
+
"--name",
|
|
783
|
+
MIDDLEBOX.table,
|
|
784
|
+
side
|
|
785
|
+
];
|
|
786
|
+
return [
|
|
787
|
+
...chain === MIDDLEBOX.input ? [[
|
|
788
|
+
"iptables",
|
|
789
|
+
"-A",
|
|
790
|
+
chain,
|
|
791
|
+
"-p",
|
|
792
|
+
"tcp",
|
|
793
|
+
"--syn",
|
|
794
|
+
...recent,
|
|
795
|
+
"--set",
|
|
796
|
+
"-j",
|
|
797
|
+
"RETURN"
|
|
798
|
+
]] : [],
|
|
799
|
+
[
|
|
800
|
+
"iptables",
|
|
801
|
+
"-A",
|
|
802
|
+
chain,
|
|
803
|
+
...recent,
|
|
804
|
+
"--update",
|
|
805
|
+
"--seconds",
|
|
806
|
+
String(time),
|
|
807
|
+
"-j",
|
|
808
|
+
"RETURN"
|
|
809
|
+
],
|
|
810
|
+
[
|
|
811
|
+
"iptables",
|
|
812
|
+
"-A",
|
|
813
|
+
chain,
|
|
814
|
+
...recent,
|
|
815
|
+
"--rcheck",
|
|
816
|
+
"-j",
|
|
817
|
+
"DROP"
|
|
818
|
+
],
|
|
819
|
+
[
|
|
820
|
+
"iptables",
|
|
821
|
+
"-A",
|
|
822
|
+
chain,
|
|
823
|
+
...recent,
|
|
824
|
+
"--set",
|
|
825
|
+
"-j",
|
|
826
|
+
"RETURN"
|
|
827
|
+
]
|
|
828
|
+
];
|
|
829
|
+
};
|
|
830
|
+
return [
|
|
831
|
+
...chains.map((chain) => [
|
|
832
|
+
"iptables",
|
|
833
|
+
"-N",
|
|
834
|
+
chain
|
|
835
|
+
]),
|
|
836
|
+
...rules(MIDDLEBOX.input, "--rsource"),
|
|
837
|
+
...rules(MIDDLEBOX.output, "--rdest"),
|
|
838
|
+
...jumps
|
|
839
|
+
];
|
|
840
|
+
};
|
|
659
841
|
/**
|
|
660
842
|
* This function gives the target of a reset or a black hole of a cell. In T07 and T18 it is Redis,
|
|
661
843
|
* with the replicas as peers. In T15 the fault is on the mesh leg between the client pods and the
|
|
@@ -825,20 +1007,49 @@ const planOf = (cell) => {
|
|
|
825
1007
|
}],
|
|
826
1008
|
restore: []
|
|
827
1009
|
};
|
|
828
|
-
case "blackhole":
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1010
|
+
case "blackhole": {
|
|
1011
|
+
const quiet = trigger.quiet === true;
|
|
1012
|
+
const retries = trigger.retries;
|
|
1013
|
+
if (retries !== void 0 && !(Number.isInteger(retries) && retries >= 1 && retries <= 255)) throw new Error(`tcp_retries2 is a count from 1 to 255, not ${retries}`);
|
|
1014
|
+
const start = quiet ? [{
|
|
1015
|
+
do: "signal",
|
|
1016
|
+
target: "clients",
|
|
1017
|
+
signal: "SIGUSR2"
|
|
1018
|
+
}] : [];
|
|
1019
|
+
const prepare = [...retries === void 0 ? [] : [{
|
|
1020
|
+
do: "sysctl",
|
|
1021
|
+
target: "clients",
|
|
1022
|
+
key: SYSCTL.retries,
|
|
1023
|
+
value: String(retries)
|
|
1024
|
+
}], ...quiet ? [{
|
|
1025
|
+
do: "signal",
|
|
1026
|
+
target: "clients",
|
|
1027
|
+
signal: "SIGUSR1"
|
|
1028
|
+
}, {
|
|
1029
|
+
do: "wait",
|
|
1030
|
+
time: QUIET_LEAD
|
|
1031
|
+
}] : []];
|
|
1032
|
+
return {
|
|
1033
|
+
...prepare.length === 0 ? {} : { prepare },
|
|
1034
|
+
inject: [{
|
|
1035
|
+
do: "inject",
|
|
1036
|
+
target: linkOf(cell),
|
|
1037
|
+
action: "blackhole"
|
|
1038
|
+
}],
|
|
1039
|
+
hold: needs(trigger.hold, "a hold"),
|
|
1040
|
+
heal: [{
|
|
1041
|
+
do: "inject",
|
|
1042
|
+
target: linkOf(cell),
|
|
1043
|
+
action: "clear"
|
|
1044
|
+
}, ...start],
|
|
1045
|
+
restore: [...retries === void 0 ? [] : [{
|
|
1046
|
+
do: "sysctl",
|
|
1047
|
+
target: "clients",
|
|
1048
|
+
key: SYSCTL.retries,
|
|
1049
|
+
value: NETNS_DEFAULTS.retries
|
|
1050
|
+
}], ...start]
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
842
1053
|
case "peer-gone-without-close": return {
|
|
843
1054
|
inject: [{
|
|
844
1055
|
do: "inject",
|
|
@@ -950,22 +1161,85 @@ const planOf = (cell) => {
|
|
|
950
1161
|
]
|
|
951
1162
|
}]
|
|
952
1163
|
};
|
|
953
|
-
case "idle":
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
target: "clients",
|
|
957
|
-
signal: "SIGUSR1"
|
|
958
|
-
}],
|
|
959
|
-
hold: needs(trigger.hold, "a hold, the time of the gap"),
|
|
960
|
-
heal: [{
|
|
1164
|
+
case "idle": {
|
|
1165
|
+
const hold = needs(trigger.hold, "a hold, the time of the gap");
|
|
1166
|
+
const start = {
|
|
961
1167
|
do: "signal",
|
|
962
1168
|
target: "clients",
|
|
963
1169
|
signal: "SIGUSR2"
|
|
1170
|
+
};
|
|
1171
|
+
if (trigger.close === true) {
|
|
1172
|
+
const close = {
|
|
1173
|
+
do: "signal",
|
|
1174
|
+
target: "clients",
|
|
1175
|
+
signal: "SIGHUP"
|
|
1176
|
+
};
|
|
1177
|
+
return {
|
|
1178
|
+
prepare: [close, {
|
|
1179
|
+
do: "wait",
|
|
1180
|
+
time: CLOSE_LEAD
|
|
1181
|
+
}],
|
|
1182
|
+
inject: [
|
|
1183
|
+
start,
|
|
1184
|
+
{
|
|
1185
|
+
do: "wait",
|
|
1186
|
+
time: CHANNEL_USE
|
|
1187
|
+
},
|
|
1188
|
+
close
|
|
1189
|
+
],
|
|
1190
|
+
hold,
|
|
1191
|
+
heal: [start],
|
|
1192
|
+
restore: [start]
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
const middlebox = trigger.middlebox;
|
|
1196
|
+
return {
|
|
1197
|
+
...middlebox === void 0 ? {} : { prepare: [{
|
|
1198
|
+
do: "middlebox",
|
|
1199
|
+
target: "servers",
|
|
1200
|
+
idle: middlebox,
|
|
1201
|
+
port: HBONE_PORT
|
|
1202
|
+
}] },
|
|
1203
|
+
inject: [{
|
|
1204
|
+
do: "signal",
|
|
1205
|
+
target: "clients",
|
|
1206
|
+
signal: "SIGUSR1"
|
|
1207
|
+
}],
|
|
1208
|
+
hold,
|
|
1209
|
+
heal: [start],
|
|
1210
|
+
restore: [start, ...middlebox === void 0 ? [] : [{
|
|
1211
|
+
do: "middlebox",
|
|
1212
|
+
target: "servers",
|
|
1213
|
+
idle: null,
|
|
1214
|
+
port: HBONE_PORT
|
|
1215
|
+
}]]
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
case "ztunnel-restart": return {
|
|
1219
|
+
inject: [{
|
|
1220
|
+
do: "kubectl",
|
|
1221
|
+
args: [
|
|
1222
|
+
"-n",
|
|
1223
|
+
"istio-system",
|
|
1224
|
+
"delete",
|
|
1225
|
+
"pod",
|
|
1226
|
+
"-l",
|
|
1227
|
+
ZTUNNEL_PODS,
|
|
1228
|
+
"--wait=false"
|
|
1229
|
+
]
|
|
964
1230
|
}],
|
|
1231
|
+
hold: null,
|
|
1232
|
+
heal: [],
|
|
965
1233
|
restore: [{
|
|
966
|
-
do: "
|
|
967
|
-
|
|
968
|
-
|
|
1234
|
+
do: "kubectl",
|
|
1235
|
+
args: [
|
|
1236
|
+
"-n",
|
|
1237
|
+
"istio-system",
|
|
1238
|
+
"rollout",
|
|
1239
|
+
"status",
|
|
1240
|
+
"daemonset/ztunnel",
|
|
1241
|
+
"--timeout=300s"
|
|
1242
|
+
]
|
|
969
1243
|
}]
|
|
970
1244
|
};
|
|
971
1245
|
}
|
|
@@ -1296,8 +1570,55 @@ const LDS_EVERY = {
|
|
|
1296
1570
|
value: 50,
|
|
1297
1571
|
unit: "ms"
|
|
1298
1572
|
};
|
|
1299
|
-
/**
|
|
1300
|
-
|
|
1573
|
+
/**
|
|
1574
|
+
* This function tells if the watcher follows each ztunnel pod and its log for a cell: a cell in the
|
|
1575
|
+
* flavor `ambient`. A cell of sidecar pods in that flavor needs the lines of the ztunnel pods too.
|
|
1576
|
+
* They show the restart of ztunnel, which its pods do not see.
|
|
1577
|
+
*/
|
|
1578
|
+
const followsZtunnel = (cell) => cell.mesh?.flavor === "ambient";
|
|
1579
|
+
/**
|
|
1580
|
+
* This function gives the arguments of kubectl that set the environment of ztunnel for a cell, or
|
|
1581
|
+
* remove it (`undo`). The variables go in the sequence of their names.
|
|
1582
|
+
*/
|
|
1583
|
+
const ztunnelEnvArgs = (env, undo) => [
|
|
1584
|
+
"-n",
|
|
1585
|
+
"istio-system",
|
|
1586
|
+
"set",
|
|
1587
|
+
"env",
|
|
1588
|
+
"daemonset/ztunnel",
|
|
1589
|
+
...Object.keys(env).sort().map((key) => undo ? `${key}-` : `${key}=${env[key]}`)
|
|
1590
|
+
];
|
|
1591
|
+
/**
|
|
1592
|
+
* This function sets the environment of ztunnel for a cell, or removes it (`undo`). Then it waits for
|
|
1593
|
+
* the rollout of the DaemonSet, and until no old ztunnel pod is left. An old pod drains for its
|
|
1594
|
+
* deadline after the rollout replaces it. Thus no run starts beside a ztunnel pod that is going.
|
|
1595
|
+
*/
|
|
1596
|
+
const setZtunnelEnv = async (cluster, env, undo) => {
|
|
1597
|
+
await cluster.kubectl(ztunnelEnvArgs(env, undo));
|
|
1598
|
+
await cluster.kubectl([
|
|
1599
|
+
"-n",
|
|
1600
|
+
"istio-system",
|
|
1601
|
+
"rollout",
|
|
1602
|
+
"status",
|
|
1603
|
+
"daemonset/ztunnel",
|
|
1604
|
+
"--timeout=300s"
|
|
1605
|
+
]);
|
|
1606
|
+
const until = Date.now() + 12e4;
|
|
1607
|
+
for (;;) {
|
|
1608
|
+
if (JSON.parse(await cluster.kubectl([
|
|
1609
|
+
"-n",
|
|
1610
|
+
"istio-system",
|
|
1611
|
+
"get",
|
|
1612
|
+
"pods",
|
|
1613
|
+
"-l",
|
|
1614
|
+
"app=ztunnel",
|
|
1615
|
+
"-o",
|
|
1616
|
+
"json"
|
|
1617
|
+
])).items.every((item) => item.metadata.deletionTimestamp === void 0)) return;
|
|
1618
|
+
if (Date.now() > until) throw new Error("an old ztunnel pod is still going 120 s after the rollout");
|
|
1619
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1301
1622
|
/** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */
|
|
1302
1623
|
const pollsListeners = (cell) => cell.mesh?.mode === "sidecar" && cell.trigger.kind === "authorization-policy";
|
|
1303
1624
|
//#endregion
|
|
@@ -1337,7 +1658,11 @@ const deletedOf = (text) => {
|
|
|
1337
1658
|
};
|
|
1338
1659
|
//#endregion
|
|
1339
1660
|
//#region src/lab.ts
|
|
1340
|
-
/**
|
|
1661
|
+
/**
|
|
1662
|
+
* These kinds of kernel record stay in the raw record of a run. The lab also keeps each change of the
|
|
1663
|
+
* state of a socket of the HBONE port (`isTunnelState`): the tunnels of ztunnel. It counts the other
|
|
1664
|
+
* records and drops them.
|
|
1665
|
+
*/
|
|
1341
1666
|
const KEPT_KERNEL = /* @__PURE__ */ new Set([
|
|
1342
1667
|
"rst_tx",
|
|
1343
1668
|
"rst_rx",
|
|
@@ -1347,6 +1672,8 @@ const KEPT_KERNEL = /* @__PURE__ */ new Set([
|
|
|
1347
1672
|
"begin",
|
|
1348
1673
|
"fail"
|
|
1349
1674
|
]);
|
|
1675
|
+
/** This function tells if the lab keeps a kernel record of the prober. */
|
|
1676
|
+
const keptKernel = (record) => KEPT_KERNEL.has(String(record["ev"])) || isTunnelState(record);
|
|
1350
1677
|
/** This container class can share the process namespace of another container. */
|
|
1351
1678
|
const shared = async () => {
|
|
1352
1679
|
const { GenericContainer } = await import("testcontainers");
|
|
@@ -1504,7 +1831,7 @@ const openKindLab = async (cluster, image) => {
|
|
|
1504
1831
|
const ev = String(record["ev"]);
|
|
1505
1832
|
if (ev === "begin") begun(true, "");
|
|
1506
1833
|
if (ev === "fail") begun(false, String(record["why"]));
|
|
1507
|
-
if (!
|
|
1834
|
+
if (!keptKernel(record) && ev !== "clock") {
|
|
1508
1835
|
dropped[ev] = (dropped[ev] ?? 0) + 1;
|
|
1509
1836
|
return;
|
|
1510
1837
|
}
|
|
@@ -1636,6 +1963,33 @@ const openKindLab = async (cluster, image) => {
|
|
|
1636
1963
|
});
|
|
1637
1964
|
return text;
|
|
1638
1965
|
},
|
|
1966
|
+
async netns(node, pid, commands) {
|
|
1967
|
+
for (const word of commands.flat()) if (!/^[A-Za-z0-9_.,:=/+-]+$/.test(word)) throw new Error(`not a plain word of a command: ${word}`);
|
|
1968
|
+
if (commands.length === 0) throw new Error("no command");
|
|
1969
|
+
const t = await lab.now();
|
|
1970
|
+
const text = await output(sidecarOf(node).injector, [
|
|
1971
|
+
"nsenter",
|
|
1972
|
+
"-t",
|
|
1973
|
+
String(pid),
|
|
1974
|
+
"-n",
|
|
1975
|
+
"sh",
|
|
1976
|
+
"-c",
|
|
1977
|
+
commands.map((argv) => argv.join(" ")).join(" && ")
|
|
1978
|
+
]);
|
|
1979
|
+
push({
|
|
1980
|
+
ev: "act",
|
|
1981
|
+
t,
|
|
1982
|
+
t1: await lab.now(),
|
|
1983
|
+
node,
|
|
1984
|
+
spec: {
|
|
1985
|
+
op: "netns",
|
|
1986
|
+
pid,
|
|
1987
|
+
commands
|
|
1988
|
+
},
|
|
1989
|
+
out: text.slice(0, 2e3)
|
|
1990
|
+
});
|
|
1991
|
+
return text;
|
|
1992
|
+
},
|
|
1639
1993
|
async now() {
|
|
1640
1994
|
return (await lab.act(firstNode, { op: "now" })).t0;
|
|
1641
1995
|
},
|
|
@@ -2642,6 +2996,46 @@ const signalClients = async (ctx, signal) => {
|
|
|
2642
2996
|
t1: Math.max(...stamps.map((stamp) => stamp.t1))
|
|
2643
2997
|
};
|
|
2644
2998
|
};
|
|
2999
|
+
/**
|
|
3000
|
+
* This function gives the network namespace of each pod of a target, as a node and a PID in it. The
|
|
3001
|
+
* pods are each client pod (`clients`), or each replica (`servers`).
|
|
3002
|
+
*/
|
|
3003
|
+
const namespacesOf = async (ctx, target, replicas) => {
|
|
3004
|
+
if (target === "servers") return Promise.all(replicas.map(async (replica) => ({
|
|
3005
|
+
node: replica.node,
|
|
3006
|
+
pod: replica.pod,
|
|
3007
|
+
pid: await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, "app")
|
|
3008
|
+
})));
|
|
3009
|
+
if (target !== "clients") throw new Error(`a sysctl or a middlebox takes the target clients or servers, not ${target}`);
|
|
3010
|
+
const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels["app"] === "client" && !pod.del && pod.node !== null);
|
|
3011
|
+
if (clients.length === 0) throw new Error("the step needs clients");
|
|
3012
|
+
return Promise.all(clients.map(async (pod) => ({
|
|
3013
|
+
node: pod.node,
|
|
3014
|
+
pod: pod.name,
|
|
3015
|
+
pid: await ctx.lab.pidOf(pod.node, ctx.namespace, pod.name, CLIENT)
|
|
3016
|
+
})));
|
|
3017
|
+
};
|
|
3018
|
+
/**
|
|
3019
|
+
* This function puts a stateful middlebox in the network namespace of each replica, or removes it.
|
|
3020
|
+
* Each client pod is a peer (`middleboxCommands`), and the nodes go at the same time. After the set,
|
|
3021
|
+
* the lab reads the filter table: it shows the chains of the middlebox. Before the removal, the lab
|
|
3022
|
+
* reads the filter table again: the counters show which segments the middlebox dropped.
|
|
3023
|
+
*/
|
|
3024
|
+
const middleboxServers = async (ctx, idle, port, replicas) => {
|
|
3025
|
+
const peers = [...ctx.tracker.pods.values()].filter((pod) => pod.labels["app"] === "client" && !pod.del && pod.ip !== null).map((pod) => pod.ip).sort();
|
|
3026
|
+
const commands = middleboxCommands(peers, idle, port);
|
|
3027
|
+
const t0 = await ctx.lab.now();
|
|
3028
|
+
const each = await namespacesOf(ctx, "servers", replicas);
|
|
3029
|
+
await Promise.all(each.map(async ({ node, pid }) => {
|
|
3030
|
+
if (idle === null) await ctx.lab.tables(node, pid, "filter");
|
|
3031
|
+
await ctx.lab.netns(node, pid, commands);
|
|
3032
|
+
if (idle !== null) await ctx.lab.tables(node, pid, "filter");
|
|
3033
|
+
}));
|
|
3034
|
+
return {
|
|
3035
|
+
t0,
|
|
3036
|
+
t1: await ctx.lab.now()
|
|
3037
|
+
};
|
|
3038
|
+
};
|
|
2645
3039
|
/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */
|
|
2646
3040
|
const doStep = async (ctx, step, replicas, background) => {
|
|
2647
3041
|
const { lab, cluster, namespace } = ctx;
|
|
@@ -2773,6 +3167,18 @@ const doStep = async (ctx, step, replicas, background) => {
|
|
|
2773
3167
|
t1: await lab.now()
|
|
2774
3168
|
};
|
|
2775
3169
|
}
|
|
3170
|
+
case "sysctl": {
|
|
3171
|
+
const command = sysctlCommand(step.key, step.value);
|
|
3172
|
+
const t0 = await lab.now();
|
|
3173
|
+
await Promise.all((await namespacesOf(ctx, step.target, replicas)).map(({ node, pid }) => lab.netns(node, pid, [command])));
|
|
3174
|
+
return {
|
|
3175
|
+
t0,
|
|
3176
|
+
t1: await lab.now()
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
case "middlebox":
|
|
3180
|
+
if (step.target !== "servers") throw new Error(`a middlebox takes the target servers, not ${step.target}`);
|
|
3181
|
+
return middleboxServers(ctx, step.idle, step.port, replicas);
|
|
2776
3182
|
}
|
|
2777
3183
|
};
|
|
2778
3184
|
/** This function tells if a run is over: the trigger is done, and the cell is at rest again. */
|
|
@@ -2791,6 +3197,7 @@ const over = (ctx, run, lines) => {
|
|
|
2791
3197
|
].includes(kind) && (ctx.cell.template === "T07" || ctx.cell.template === "T15")) return kind === "scale-in" || kind === "node-drain" ? run.replicas.some((replica) => !ctx.tracker.pods.has(replica.pod)) : !ctx.tracker.pods.has(run.target.pod);
|
|
2792
3198
|
if (kind === "rolling-update" || kind === "rollout") return run.replicas.every((replica) => !ctx.tracker.pods.has(replica.pod));
|
|
2793
3199
|
if (kind === "app-stall") return lines.some((line) => line.ev === "stall" && line["phase"] === "end");
|
|
3200
|
+
if (kind === "ztunnel-restart") return ztunnelReplaced(lines, ctx.cluster.nodes);
|
|
2794
3201
|
return true;
|
|
2795
3202
|
};
|
|
2796
3203
|
/** This function gives the replica that the trigger stopped. For a scale-in or a drain, the controller chooses it. */
|
|
@@ -2848,6 +3255,14 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
|
|
|
2848
3255
|
const notes = [];
|
|
2849
3256
|
const forgotten = Object.entries(cleared).filter(([, count]) => count > 0);
|
|
2850
3257
|
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(", ")}`);
|
|
3258
|
+
for (const step of plan.prepare ?? []) try {
|
|
3259
|
+
await doStep(ctx, step, replicas, background);
|
|
3260
|
+
} catch (error) {
|
|
3261
|
+
const why = `the step ${step.do} before the fault failed: ${String(error).slice(0, 400)}`;
|
|
3262
|
+
ctx.log(` ${why}`);
|
|
3263
|
+
for (const each of plan.restore) await doStep(ctx, each, replicas, background).catch((failure) => ctx.log(` the restore ${each.do} failed: ${String(failure)}`));
|
|
3264
|
+
return empty(why, start);
|
|
3265
|
+
}
|
|
2851
3266
|
for (const step of plan.inject) {
|
|
2852
3267
|
const stamp = await doStep(ctx, step, replicas, background);
|
|
2853
3268
|
if (stamp !== null && fault === null) fault = stamp.t0;
|
|
@@ -3050,7 +3465,9 @@ const runKindCells = async (cells, options) => {
|
|
|
3050
3465
|
const tracker = new Tracker();
|
|
3051
3466
|
const off = lab.listen(tracker.see);
|
|
3052
3467
|
const loads = [];
|
|
3468
|
+
const ztunnelEnv = cell.mesh?.ztunnel;
|
|
3053
3469
|
try {
|
|
3470
|
+
if (ztunnelEnv !== void 0) await setZtunnelEnv(cluster, ztunnelEnv, false);
|
|
3054
3471
|
const clusterIp = await deploy(lab, cluster, cell, namespace, images);
|
|
3055
3472
|
for (const name of [...lab.own(), loader.getName().replace(/^\//, "")]) ours.add(name);
|
|
3056
3473
|
const ctx = {
|
|
@@ -3112,6 +3529,7 @@ const runKindCells = async (cells, options) => {
|
|
|
3112
3529
|
"--wait=true",
|
|
3113
3530
|
"--timeout=300s"
|
|
3114
3531
|
]).catch((error) => options.log(` ${String(error)}`));
|
|
3532
|
+
if (ztunnelEnv !== void 0) await setZtunnelEnv(cluster, ztunnelEnv, true).catch((error) => options.log(` the undo of the environment of ztunnel failed: ${String(error)}`));
|
|
3115
3533
|
}
|
|
3116
3534
|
}
|
|
3117
3535
|
} finally {
|
|
@@ -3149,12 +3567,14 @@ const estimate = (cell, environments = 2) => {
|
|
|
3149
3567
|
"cpu-throttle": hold + 40,
|
|
3150
3568
|
rollout: hold + 50,
|
|
3151
3569
|
"authorization-policy": hold + 20,
|
|
3152
|
-
idle: hold + 5
|
|
3570
|
+
idle: hold + 5,
|
|
3571
|
+
"ztunnel-restart": 40
|
|
3153
3572
|
};
|
|
3154
3573
|
const after = observationAfter(cell);
|
|
3155
3574
|
const observed = after === null ? 0 : hold + valueIn(after, "s");
|
|
3156
3575
|
const closes = cell.prediction.kind === "ends" && cell.prediction.band !== null ? valueIn(cell.prediction.band.hi, "s") + 3 : 0;
|
|
3157
|
-
const
|
|
3576
|
+
const quiet = cell.trigger.quiet === true ? hold + 10 : 0;
|
|
3577
|
+
const each = 25 + Math.max(observed, closes, quiet, trigger[cell.trigger.kind] ?? (cell.template === "T18" ? hold + 35 : 15 + valueIn(cell.pod.preStop, "s")));
|
|
3158
3578
|
return (cell.mesh === void 0 ? 60 : 180) * environments + (cell.runs + environments) * each;
|
|
3159
3579
|
};
|
|
3160
3580
|
/**
|
|
@@ -3186,6 +3606,6 @@ const shardOf = (cells, index, count) => {
|
|
|
3186
3606
|
return out;
|
|
3187
3607
|
};
|
|
3188
3608
|
//#endregion
|
|
3189
|
-
export { ACCESS_LOG_FORMAT, CLIENT, CLIENT_ACCOUNT, CONTROLLER, DOCUMENTED, DRAIN_RULES, EDS_EVERY, FLAG_CHECK, FORMAT, FORMAT_V1, HEALTH_PORT, ISTIOCTL, ISTIO_IMAGES, ISTIO_VERSION, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LDS_EVERY, LDS_FILTER, LOG_LABEL, MESH_IMAGES, MESH_WORKLOADS, NODE_IMAGES, O04, O09, O10, O16, POLICY, PORT, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, STAGES, WATCHDOG, backendsAt, backendsOf, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, checkRun, cidrOverlap, cidrRange, clientObjects, clusterFilter, compose, createCluster, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, followsZtunnel, goDuration, hostsOf, installMesh, intakeAfter, intakeOf, istioOperator, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, latchBand, lintCell, listenerCountsOf, localPin, medianInterval, medianText, meshKey, middle, namespaceObject, namespaceOf, nodeNames, observationAfter, observationEnd, openKindLab, outboundCluster, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, policyObject, pollsListeners, predictionText, probeOf, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverCluster, serverObjects, sha256, shardOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, socketsOf, stackOf, stageLabImage, stallRemovalBand, stoppingOf, tarEntry, timeSpread, toolPath, transitionOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, writeResult };
|
|
3609
|
+
export { ACCESS_LOG_FORMAT, CHANNEL_USE, CLIENT, CLIENT_ACCOUNT, CLOSE_LEAD, CONTROLLER, DOCUMENTED, DRAIN_RULES, EDS_EVERY, FLAG_CHECK, FORMAT, FORMAT_V1, HBONE_PORT, HEALTH_PORT, ISTIOCTL, ISTIO_IMAGES, ISTIO_VERSION, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LDS_EVERY, LDS_FILTER, LOG_LABEL, MESH_IMAGES, MESH_WORKLOADS, MIDDLEBOX, NETNS_DEFAULTS, NODE_IMAGES, O04, O09, O10, O16, POLICY, PORT, QUIET_LEAD, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, STAGES, SYSCTL, TCP_STATE, WATCHDOG, ZTUNNEL_PODS, backendsAt, backendsOf, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, checkRun, cidrOverlap, cidrRange, clientObjects, clusterFilter, compose, createCluster, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, followsZtunnel, goDuration, hostsOf, installMesh, intakeAfter, intakeOf, isTunnelState, istioOperator, judge, judgeKind, keptKernel, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, latchBand, lintCell, listenerCountsOf, localPin, medianInterval, medianText, meshKey, middle, middleboxCommands, namespaceObject, namespaceOf, nodeNames, observationAfter, observationEnd, openKindLab, outboundCluster, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, policyObject, pollsListeners, predictionText, probeOf, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartOf, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverCluster, serverObjects, setZtunnelEnv, sha256, shardOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, socketsOf, stackOf, stageLabImage, stallRemovalBand, stoppingOf, sysctlCommand, tarEntry, timeSpread, toolPath, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, writeResult, ztunnelEnvArgs, ztunnelPodsOf, ztunnelReplaced };
|
|
3190
3610
|
|
|
3191
3611
|
//# sourceMappingURL=index.js.map
|