@mark1russell7/kind 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/build/index.d.ts +51 -11
- package/build/index.js +230 -53
- package/build/index.js.map +1 -1
- package/build/{judge-DMuI3aDn.d.ts → judge-BS9pk96o.d.ts} +2 -2
- package/build/judge.d.ts +1 -1
- package/image/watch.mjs +30 -3
- package/package.json +3 -3
package/build/index.js
CHANGED
|
@@ -295,6 +295,22 @@ const writeWhole = (path, bytes, executable) => {
|
|
|
295
295
|
if (executable) chmodSync(partial, 493);
|
|
296
296
|
renameSync(partial, path);
|
|
297
297
|
};
|
|
298
|
+
/** These are the waits in ms before the second and the third try of a download. A runner can fail to connect for a moment. */
|
|
299
|
+
const DOWNLOAD_WAITS = [2e3, 8e3];
|
|
300
|
+
/**
|
|
301
|
+
* This function gives the bytes of a URL. It tries again after each wait when the connection fails or
|
|
302
|
+
* the answer is a 5xx. It refuses each other answer that is not ok at once.
|
|
303
|
+
*/
|
|
304
|
+
const download = async (url, waits = DOWNLOAD_WAITS) => {
|
|
305
|
+
for (let attempt = 0;; attempt++) {
|
|
306
|
+
const response = await fetch(url, { redirect: "follow" }).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
307
|
+
if (response instanceof Response && response.ok) return new Uint8Array(await response.arrayBuffer());
|
|
308
|
+
const failure = response instanceof Response ? /* @__PURE__ */ new Error(`the download of ${url} answered ${response.status}`) : response;
|
|
309
|
+
const wait = waits[attempt];
|
|
310
|
+
if (response instanceof Response && response.status < 500 || wait === void 0) throw failure;
|
|
311
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
312
|
+
}
|
|
313
|
+
};
|
|
298
314
|
/**
|
|
299
315
|
* This function gives the path of a tool, after a download when the cache does not have it. It
|
|
300
316
|
* refuses bytes whose sha256 is not the pinned value, from the cache or from the download. For a
|
|
@@ -314,9 +330,7 @@ const ensureTool = async (tool, key = platformKey(), cache = cacheFolder()) => {
|
|
|
314
330
|
if (found !== asset.sha256) throw new Error(`${kept} has the sha256 ${found}, not the pinned ${asset.sha256}`);
|
|
315
331
|
if (tool.entry === void 0) return path;
|
|
316
332
|
} else {
|
|
317
|
-
|
|
318
|
-
if (!response.ok) throw new Error(`the download of ${asset.url} answered ${response.status}`);
|
|
319
|
-
bytes = new Uint8Array(await response.arrayBuffer());
|
|
333
|
+
bytes = await download(asset.url);
|
|
320
334
|
const found = sha256(bytes);
|
|
321
335
|
if (found !== asset.sha256) throw new Error(`${asset.url} has the sha256 ${found}, not the pinned ${asset.sha256}`);
|
|
322
336
|
writeWhole(kept, bytes, executable && tool.entry === void 0);
|
|
@@ -642,6 +656,12 @@ const policyObject = (account) => ({
|
|
|
642
656
|
rules: [{ from: [{ source: { principals: [`cluster.local/ns/{namespace}/sa/${account}`] } }] }]
|
|
643
657
|
}
|
|
644
658
|
});
|
|
659
|
+
/**
|
|
660
|
+
* This function gives the target of a reset or a black hole of a cell. In T07 and T18 it is Redis,
|
|
661
|
+
* with the replicas as peers. In T15 the fault is on the mesh leg between the client pods and the
|
|
662
|
+
* replicas. It is a rule in each replica, with the client pods as peers.
|
|
663
|
+
*/
|
|
664
|
+
const linkOf = (cell) => cell.template === "T15" ? "servers" : "redis";
|
|
645
665
|
/** This function gives the fault plan of a cell. */
|
|
646
666
|
const planOf = (cell) => {
|
|
647
667
|
const ns = ["-n", "{namespace}"];
|
|
@@ -794,13 +814,13 @@ const planOf = (cell) => {
|
|
|
794
814
|
case "rst": return {
|
|
795
815
|
inject: [{
|
|
796
816
|
do: "inject",
|
|
797
|
-
target:
|
|
817
|
+
target: linkOf(cell),
|
|
798
818
|
action: "rst"
|
|
799
819
|
}],
|
|
800
820
|
hold: needs(trigger.hold, "a hold"),
|
|
801
821
|
heal: [{
|
|
802
822
|
do: "inject",
|
|
803
|
-
target:
|
|
823
|
+
target: linkOf(cell),
|
|
804
824
|
action: "clear-rst"
|
|
805
825
|
}],
|
|
806
826
|
restore: []
|
|
@@ -808,13 +828,13 @@ const planOf = (cell) => {
|
|
|
808
828
|
case "blackhole": return {
|
|
809
829
|
inject: [{
|
|
810
830
|
do: "inject",
|
|
811
|
-
target:
|
|
831
|
+
target: linkOf(cell),
|
|
812
832
|
action: "blackhole"
|
|
813
833
|
}],
|
|
814
834
|
hold: needs(trigger.hold, "a hold"),
|
|
815
835
|
heal: [{
|
|
816
836
|
do: "inject",
|
|
817
|
-
target:
|
|
837
|
+
target: linkOf(cell),
|
|
818
838
|
action: "clear"
|
|
819
839
|
}],
|
|
820
840
|
restore: []
|
|
@@ -1041,9 +1061,14 @@ const stageLabImage = () => {
|
|
|
1041
1061
|
return context;
|
|
1042
1062
|
};
|
|
1043
1063
|
let built = null;
|
|
1044
|
-
/**
|
|
1064
|
+
/**
|
|
1065
|
+
* This function builds the lab image one time for each process, and gives its tag. Docker keeps its
|
|
1066
|
+
* layers between builds. A build that fails leaves the cache. Thus a later call builds again after a
|
|
1067
|
+
* fault of Docker or of the network.
|
|
1068
|
+
*/
|
|
1045
1069
|
const labImage = () => {
|
|
1046
|
-
built
|
|
1070
|
+
if (built !== null) return built;
|
|
1071
|
+
const building = (async () => {
|
|
1047
1072
|
const context = stageLabImage();
|
|
1048
1073
|
try {
|
|
1049
1074
|
const tag = contentTag("systems-kind-lab", context);
|
|
@@ -1057,7 +1082,11 @@ const labImage = () => {
|
|
|
1057
1082
|
});
|
|
1058
1083
|
}
|
|
1059
1084
|
})();
|
|
1060
|
-
|
|
1085
|
+
building.catch(() => {
|
|
1086
|
+
if (built === building) built = null;
|
|
1087
|
+
});
|
|
1088
|
+
built = building;
|
|
1089
|
+
return building;
|
|
1061
1090
|
};
|
|
1062
1091
|
//#endregion
|
|
1063
1092
|
//#region src/istio.ts
|
|
@@ -1069,14 +1098,27 @@ const labImage = () => {
|
|
|
1069
1098
|
/** This version is the release of Istio of the lab: the tag of istioctl and of each image. */
|
|
1070
1099
|
const ISTIO_VERSION = "1.31.3";
|
|
1071
1100
|
/**
|
|
1072
|
-
* These are the images of
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1101
|
+
* These are the images of Istio, pinned by the digest of their index on Docker Hub (read on
|
|
1102
|
+
* 2026-10-24). `gcr.io/istio-release` has no final tag of 1.31. The lab pulls each one by its digest,
|
|
1103
|
+
* tags it with that digest (`localPin`), and loads it into each node. Thus no node pulls from a
|
|
1104
|
+
* registry. The flavor `sidecar` needs istiod and the proxy. The flavor `ambient` also needs
|
|
1105
|
+
* istio-cni and ztunnel (`MESH_IMAGES`).
|
|
1076
1106
|
*/
|
|
1077
1107
|
const ISTIO_IMAGES = {
|
|
1078
1108
|
pilot: "istio/pilot:1.31.3@sha256:6ad1e816c4d4767ba3f30a6286a96d121851ddb23d33e20736758acb5f3f4ea9",
|
|
1079
|
-
proxy: "istio/proxyv2:1.31.3@sha256:eb74eacea98d3fb1e239ea84016085155478586a554158676b9b67d960151084"
|
|
1109
|
+
proxy: "istio/proxyv2:1.31.3@sha256:eb74eacea98d3fb1e239ea84016085155478586a554158676b9b67d960151084",
|
|
1110
|
+
cni: "istio/install-cni:1.31.3@sha256:e98782038634ef4fadaf78b53dd8fa4134fda3d65838ead5fc1edef245451c0f",
|
|
1111
|
+
ztunnel: "istio/ztunnel:1.31.3@sha256:2c27371b3139ecbf4f5a017354bd540889482441712e88cd8e654ead5a3eb5b2"
|
|
1112
|
+
};
|
|
1113
|
+
/** These are the images that each flavor installs, by their names in `ISTIO_IMAGES`. */
|
|
1114
|
+
const MESH_IMAGES = {
|
|
1115
|
+
sidecar: ["pilot", "proxy"],
|
|
1116
|
+
ambient: [
|
|
1117
|
+
"pilot",
|
|
1118
|
+
"proxy",
|
|
1119
|
+
"cni",
|
|
1120
|
+
"ztunnel"
|
|
1121
|
+
]
|
|
1080
1122
|
};
|
|
1081
1123
|
/**
|
|
1082
1124
|
* This format is the JSON access log of each sidecar. Each field is a command of the formatter of
|
|
@@ -1110,10 +1152,15 @@ const ACCESS_LOG_FORMAT = {
|
|
|
1110
1152
|
* reports the whole runner, and no cell scales istiod.
|
|
1111
1153
|
* - The access log of each sidecar goes to its output as JSON (`ACCESS_LOG_FORMAT`).
|
|
1112
1154
|
*
|
|
1113
|
-
* The flavor `ambient`
|
|
1155
|
+
* The flavor `ambient` has the profile `ambient`: istiod, istio-cni and ztunnel, and no gateway
|
|
1156
|
+
* (manifests/profiles/ambient.yaml of Istio 1.31.3). Its two other images are full references too
|
|
1157
|
+
* (templates/daemonset.yaml of the chart of istio-cni, lines 93–96, and of ztunnel, lines 65–68).
|
|
1158
|
+
* The chart of istio-cni takes the pull policy of `global`. The template of ztunnel reads only its own
|
|
1159
|
+
* `imagePullPolicy`, thus the operator sets it there too.
|
|
1114
1160
|
*/
|
|
1115
1161
|
const istioOperator = (flavor, images) => {
|
|
1116
|
-
|
|
1162
|
+
const ambient = flavor === "ambient";
|
|
1163
|
+
if (ambient && (images.cni === void 0 || images.ztunnel === void 0)) throw new Error("the flavor ambient needs the images of istio-cni and ztunnel");
|
|
1117
1164
|
return {
|
|
1118
1165
|
apiVersion: "install.istio.io/v1alpha1",
|
|
1119
1166
|
kind: "IstioOperator",
|
|
@@ -1122,7 +1169,7 @@ const istioOperator = (flavor, images) => {
|
|
|
1122
1169
|
namespace: "istio-system"
|
|
1123
1170
|
},
|
|
1124
1171
|
spec: {
|
|
1125
|
-
profile: "minimal",
|
|
1172
|
+
profile: ambient ? "ambient" : "minimal",
|
|
1126
1173
|
hub: "docker.io/istio",
|
|
1127
1174
|
tag: ISTIO_VERSION,
|
|
1128
1175
|
meshConfig: {
|
|
@@ -1144,37 +1191,77 @@ const istioOperator = (flavor, images) => {
|
|
|
1144
1191
|
cpu: "500m",
|
|
1145
1192
|
memory: "256Mi"
|
|
1146
1193
|
} }
|
|
1147
|
-
}
|
|
1194
|
+
},
|
|
1195
|
+
...ambient ? {
|
|
1196
|
+
cni: { image: images.cni },
|
|
1197
|
+
ztunnel: {
|
|
1198
|
+
image: images.ztunnel,
|
|
1199
|
+
imagePullPolicy: "Never"
|
|
1200
|
+
}
|
|
1201
|
+
} : {}
|
|
1148
1202
|
}
|
|
1149
1203
|
}
|
|
1150
1204
|
};
|
|
1151
1205
|
};
|
|
1206
|
+
/** These are the workloads of each flavor in istio-system that the install waits for. */
|
|
1207
|
+
const MESH_WORKLOADS = {
|
|
1208
|
+
sidecar: ["deployment/istiod"],
|
|
1209
|
+
ambient: [
|
|
1210
|
+
"deployment/istiod",
|
|
1211
|
+
"daemonset/istio-cni-node",
|
|
1212
|
+
"daemonset/ztunnel"
|
|
1213
|
+
]
|
|
1214
|
+
};
|
|
1152
1215
|
/**
|
|
1153
1216
|
* This function installs the mesh of a flavor in a cluster with `istioctl install` and the operator
|
|
1154
|
-
* of the flavor. Then it waits for
|
|
1217
|
+
* of the flavor. Then it waits for each workload of the flavor. The kubeconfig of the cluster goes to
|
|
1218
|
+
* istioctl. When the install fails, the error has the pods of istio-system and the last lines of the
|
|
1219
|
+
* log of istio-cni. Thus the log of the job shows the cause, for example a module that the kernel of
|
|
1220
|
+
* the runner does not have.
|
|
1155
1221
|
*/
|
|
1156
1222
|
const installMesh = async (cluster, flavor, images) => {
|
|
1157
1223
|
const istioctl = await ensureTool(ISTIOCTL);
|
|
1158
1224
|
const file = join(cluster.dir, `istio-${flavor}.json`);
|
|
1159
1225
|
writeFileSync(file, `${JSON.stringify(istioOperator(flavor, images), null, 2)}\n`);
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1226
|
+
try {
|
|
1227
|
+
await command(istioctl, [
|
|
1228
|
+
"install",
|
|
1229
|
+
"--kubeconfig",
|
|
1230
|
+
cluster.kubeconfig,
|
|
1231
|
+
"-y",
|
|
1232
|
+
"-f",
|
|
1233
|
+
file,
|
|
1234
|
+
"--readiness-timeout",
|
|
1235
|
+
"10m0s"
|
|
1236
|
+
], { timeout: 9e5 });
|
|
1237
|
+
for (const workload of MESH_WORKLOADS[flavor]) await cluster.kubectl([
|
|
1238
|
+
"-n",
|
|
1239
|
+
"istio-system",
|
|
1240
|
+
"rollout",
|
|
1241
|
+
"status",
|
|
1242
|
+
workload,
|
|
1243
|
+
"--timeout=300s"
|
|
1244
|
+
]);
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
const state = async (args) => (await cluster.kubectl([
|
|
1247
|
+
"-n",
|
|
1248
|
+
"istio-system",
|
|
1249
|
+
...args
|
|
1250
|
+
]).catch((why) => String(why))).slice(-4e3);
|
|
1251
|
+
const pods = await state([
|
|
1252
|
+
"get",
|
|
1253
|
+
"pods",
|
|
1254
|
+
"-o",
|
|
1255
|
+
"wide"
|
|
1256
|
+
]);
|
|
1257
|
+
const cni = flavor === "ambient" ? `\nthe log of istio-cni:\n${await state([
|
|
1258
|
+
"logs",
|
|
1259
|
+
"daemonset/istio-cni-node",
|
|
1260
|
+
"--tail=60",
|
|
1261
|
+
"--all-containers"
|
|
1262
|
+
])}` : "";
|
|
1263
|
+
throw new Error(`the install of Istio ${flavor} failed: ${String(error)}\nthe pods of istio-system:\n${pods}${cni}`);
|
|
1264
|
+
}
|
|
1178
1265
|
};
|
|
1179
1266
|
/** This function gives the key of the mesh of a cell for its cluster: its flavor, or `none`. */
|
|
1180
1267
|
const meshKey = (cell) => cell.mesh?.flavor ?? "none";
|
|
@@ -1190,6 +1277,8 @@ const LDS_EVERY = {
|
|
|
1190
1277
|
value: 50,
|
|
1191
1278
|
unit: "ms"
|
|
1192
1279
|
};
|
|
1280
|
+
/** This function tells if the watcher follows the log of each ztunnel for a cell: a cell in ambient mode. */
|
|
1281
|
+
const followsZtunnel = (cell) => cell.mesh?.mode === "ambient";
|
|
1193
1282
|
/** This function tells if the lab polls the LDS updates of the server sidecars of a cell: a policy in a sidecar mesh. */
|
|
1194
1283
|
const pollsListeners = (cell) => cell.mesh?.mode === "sidecar" && cell.trigger.kind === "authorization-policy";
|
|
1195
1284
|
//#endregion
|
|
@@ -1377,12 +1466,13 @@ const openKindLab = async (cluster, image) => {
|
|
|
1377
1466
|
listeners.add(listener);
|
|
1378
1467
|
return () => listeners.delete(listener);
|
|
1379
1468
|
},
|
|
1380
|
-
async watch(namespaces) {
|
|
1469
|
+
async watch(namespaces, options) {
|
|
1381
1470
|
if (watcher !== null) await watcher.stop({ timeout: 0 }).catch(() => void 0);
|
|
1382
1471
|
const kube = await cluster.credentials();
|
|
1383
1472
|
watcher = await new GenericContainer(image).withNetworkMode(cluster.network).withEnvironment({
|
|
1384
1473
|
KUBE: JSON.stringify(kube),
|
|
1385
|
-
NAMESPACES: namespaces.join(",")
|
|
1474
|
+
NAMESPACES: namespaces.join(","),
|
|
1475
|
+
ZTUNNEL: options?.ztunnel === true ? "1" : "0"
|
|
1386
1476
|
}).withCommand(["node", "watch.mjs"]).withLogConsumer(consume((line) => push(line))).start();
|
|
1387
1477
|
},
|
|
1388
1478
|
async probe(netns) {
|
|
@@ -1505,6 +1595,28 @@ const openKindLab = async (cluster, image) => {
|
|
|
1505
1595
|
});
|
|
1506
1596
|
return stamp;
|
|
1507
1597
|
},
|
|
1598
|
+
async tables(node, pid, table) {
|
|
1599
|
+
const t = await lab.now();
|
|
1600
|
+
const text = await output(sidecarOf(node).injector, [
|
|
1601
|
+
"nsenter",
|
|
1602
|
+
"-t",
|
|
1603
|
+
String(pid),
|
|
1604
|
+
"-n",
|
|
1605
|
+
"iptables-save",
|
|
1606
|
+
"-c",
|
|
1607
|
+
"-t",
|
|
1608
|
+
table
|
|
1609
|
+
]);
|
|
1610
|
+
push({
|
|
1611
|
+
ev: "tables",
|
|
1612
|
+
t,
|
|
1613
|
+
node,
|
|
1614
|
+
pid,
|
|
1615
|
+
table,
|
|
1616
|
+
text: text.slice(0, 8e3)
|
|
1617
|
+
});
|
|
1618
|
+
return text;
|
|
1619
|
+
},
|
|
1508
1620
|
async now() {
|
|
1509
1621
|
return (await lab.act(firstNode, { op: "now" })).t0;
|
|
1510
1622
|
},
|
|
@@ -1591,16 +1703,23 @@ const whole = (amount, what) => {
|
|
|
1591
1703
|
};
|
|
1592
1704
|
/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */
|
|
1593
1705
|
const namespaceOf = (cell) => `lab-${createHash("sha256").update(cell.name).digest("hex").slice(0, 10)}`;
|
|
1706
|
+
/** These are the labels of the namespace of each mode of a mesh. */
|
|
1707
|
+
const MESH_LABELS = {
|
|
1708
|
+
sidecar: { "istio-injection": "enabled" },
|
|
1709
|
+
ambient: { "istio.io/dataplane-mode": "ambient" }
|
|
1710
|
+
};
|
|
1594
1711
|
/**
|
|
1595
1712
|
* This function gives the Namespace object of a cell. In the sidecar mode of a mesh, the label
|
|
1596
1713
|
* `istio-injection=enabled` makes the injector of Istio put a sidecar in each pod of the namespace.
|
|
1714
|
+
* In the ambient mode, the label `istio.io/dataplane-mode=ambient` makes istio-cni redirect the
|
|
1715
|
+
* traffic of each pod to ztunnel. The listeners of ztunnel are in the network namespace of the pod.
|
|
1597
1716
|
*/
|
|
1598
1717
|
const namespaceObject = (cell, namespace) => ({
|
|
1599
1718
|
apiVersion: "v1",
|
|
1600
1719
|
kind: "Namespace",
|
|
1601
1720
|
metadata: {
|
|
1602
1721
|
name: namespace,
|
|
1603
|
-
...cell.mesh
|
|
1722
|
+
...cell.mesh === void 0 ? {} : { labels: { ...MESH_LABELS[cell.mesh.mode] } }
|
|
1604
1723
|
}
|
|
1605
1724
|
});
|
|
1606
1725
|
/**
|
|
@@ -2287,7 +2406,7 @@ const quiet = async (ctx, ours) => {
|
|
|
2287
2406
|
*/
|
|
2288
2407
|
const deploy = async (lab, cluster, cell, namespace, images) => {
|
|
2289
2408
|
await cluster.apply([namespaceObject(cell, namespace)]);
|
|
2290
|
-
await lab.watch([namespace]);
|
|
2409
|
+
await lab.watch([namespace], { ztunnel: followsZtunnel(cell) });
|
|
2291
2410
|
if (hasRedis(cell)) {
|
|
2292
2411
|
await cluster.apply(redisObjects(namespace, images.redis));
|
|
2293
2412
|
await cluster.kubectl([
|
|
@@ -2423,12 +2542,62 @@ const replicasOf = (ctx) => {
|
|
|
2423
2542
|
}));
|
|
2424
2543
|
return stackOf(ctx.cell) === "grpc" ? byClients(replicas, ctx.tracker.answers) : replicas;
|
|
2425
2544
|
};
|
|
2545
|
+
/**
|
|
2546
|
+
* This time is how long before the commit of its rules the lab starts the injector in each replica.
|
|
2547
|
+
* It is more than the execs of one node before its commit: a read of its tables and the injector.
|
|
2548
|
+
*/
|
|
2549
|
+
const INJECT_LEAD_MS = 700;
|
|
2550
|
+
/**
|
|
2551
|
+
* This function puts the rules of an action in the network namespace of each replica, with each
|
|
2552
|
+
* client pod as a peer: the target `servers`. The rules of all replicas commit at one instant on
|
|
2553
|
+
* CLOCK_MONOTONIC (`--at`). Thus no client meets one replica with the rule and one without it. The
|
|
2554
|
+
* replicas of one node go one after the other, and the nodes at the same time.
|
|
2555
|
+
*
|
|
2556
|
+
* The fault finds the PID of each replica, and the clear uses it again: thus the heal comes soon
|
|
2557
|
+
* after its hold. Before the commit of a clear, the lab reads the filter table of each replica. Its
|
|
2558
|
+
* counters show if the rule matched the clients beside the rules of istio-cni. After the commit of a
|
|
2559
|
+
* fault, the lab reads the nat table, with the redirect of istio-cni. The stamp is the first commit
|
|
2560
|
+
* and the last end.
|
|
2561
|
+
*/
|
|
2562
|
+
const injectServers = async (ctx, action, replicas) => {
|
|
2563
|
+
const clients = [...ctx.tracker.pods.values()].filter((pod) => pod.labels["app"] === "client" && !pod.del && pod.ip !== null).map((pod) => pod.ip).sort();
|
|
2564
|
+
if (clients.length === 0 || replicas.length === 0) throw new Error(`the injector needs clients and replicas: ${clients.length} clients, ${replicas.length} replicas`);
|
|
2565
|
+
const clear = action.startsWith("clear");
|
|
2566
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
2567
|
+
for (const replica of replicas) {
|
|
2568
|
+
const pid = (clear ? ctx.pids.get(replica.pod) : void 0) ?? await ctx.lab.pidOf(replica.node, ctx.namespace, replica.pod, "app");
|
|
2569
|
+
ctx.pids.set(replica.pod, pid);
|
|
2570
|
+
byNode.set(replica.node, [...byNode.get(replica.node) ?? [], pid]);
|
|
2571
|
+
}
|
|
2572
|
+
const at = await ctx.lab.now() + INJECT_LEAD_MS;
|
|
2573
|
+
const args = [
|
|
2574
|
+
action,
|
|
2575
|
+
clients.join(","),
|
|
2576
|
+
"--at",
|
|
2577
|
+
String(Math.round(at * 1e6))
|
|
2578
|
+
];
|
|
2579
|
+
const stamps = await Promise.all([...byNode].map(async ([node, pids]) => {
|
|
2580
|
+
const out = [];
|
|
2581
|
+
for (const pid of pids) {
|
|
2582
|
+
if (clear) await ctx.lab.tables(node, pid, "filter");
|
|
2583
|
+
out.push(await ctx.lab.inject(node, pid, args));
|
|
2584
|
+
}
|
|
2585
|
+
return out;
|
|
2586
|
+
}));
|
|
2587
|
+
if (!clear) for (const [node, pids] of byNode) for (const pid of pids) await ctx.lab.tables(node, pid, "nat");
|
|
2588
|
+
const all = stamps.flat();
|
|
2589
|
+
return {
|
|
2590
|
+
t0: Math.min(...all.map((stamp) => stamp.t0)),
|
|
2591
|
+
t1: Math.max(...all.map((stamp) => stamp.t1))
|
|
2592
|
+
};
|
|
2593
|
+
};
|
|
2426
2594
|
/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */
|
|
2427
2595
|
const doStep = async (ctx, step, replicas, background) => {
|
|
2428
2596
|
const { lab, cluster, namespace } = ctx;
|
|
2429
2597
|
const target = replicas[0];
|
|
2430
2598
|
const fill = (args) => args.map((arg) => arg.replaceAll("{namespace}", namespace).replaceAll(POD, target.pod).replaceAll(NODE, target.node).replaceAll(VERSION, String(ctx.version)));
|
|
2431
2599
|
const where = async (which) => {
|
|
2600
|
+
if (which === "servers") throw new Error("the target servers is a target of the injector only");
|
|
2432
2601
|
if (which === "server") return {
|
|
2433
2602
|
node: target.node,
|
|
2434
2603
|
pid: await lab.pidOf(target.node, namespace, target.pod, "app"),
|
|
@@ -2502,6 +2671,7 @@ const doStep = async (ctx, step, replicas, background) => {
|
|
|
2502
2671
|
};
|
|
2503
2672
|
}
|
|
2504
2673
|
case "inject": {
|
|
2674
|
+
if (step.target === "servers") return injectServers(ctx, step.action, replicas);
|
|
2505
2675
|
const at = await where(step.target);
|
|
2506
2676
|
let first = null;
|
|
2507
2677
|
for (const replica of replicas) {
|
|
@@ -2768,14 +2938,19 @@ const runKindCells = async (cells, options) => {
|
|
|
2768
2938
|
kind: "shipped",
|
|
2769
2939
|
name: "grpc-go"
|
|
2770
2940
|
}) : void 0;
|
|
2941
|
+
const flavors = new Set(cells.flatMap((cell) => cell.mesh === void 0 ? [] : [cell.mesh.flavor]));
|
|
2942
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
2943
|
+
for (const name of new Set([...flavors].flatMap((flavor) => MESH_IMAGES[flavor]))) pinned.set(name, await localPin(ISTIO_IMAGES[name]));
|
|
2771
2944
|
const images = {
|
|
2772
2945
|
lab: image,
|
|
2773
2946
|
redis: redisImage,
|
|
2774
2947
|
grpc: grpcImage,
|
|
2775
|
-
mesh:
|
|
2776
|
-
pilot:
|
|
2777
|
-
proxy:
|
|
2778
|
-
|
|
2948
|
+
mesh: flavors.size === 0 ? void 0 : {
|
|
2949
|
+
pilot: pinned.get("pilot"),
|
|
2950
|
+
proxy: pinned.get("proxy"),
|
|
2951
|
+
cni: pinned.get("cni"),
|
|
2952
|
+
ztunnel: pinned.get("ztunnel")
|
|
2953
|
+
}
|
|
2779
2954
|
};
|
|
2780
2955
|
const groups = /* @__PURE__ */ new Map();
|
|
2781
2956
|
for (const cell of cells) groups.set(clusterKey(cell), [...groups.get(clusterKey(cell)) ?? [], cell]);
|
|
@@ -2796,7 +2971,11 @@ const runKindCells = async (cells, options) => {
|
|
|
2796
2971
|
if (grpcImage !== void 0 && group.some((cell) => stackOf(cell) === "grpc")) await cluster.load(grpcImage);
|
|
2797
2972
|
if (mesh !== void 0) {
|
|
2798
2973
|
if (images.mesh === void 0) throw new Error(`${group[0].name}: the run has no images of Istio`);
|
|
2799
|
-
for (const
|
|
2974
|
+
for (const name of MESH_IMAGES[mesh.flavor]) {
|
|
2975
|
+
const each = images.mesh[name];
|
|
2976
|
+
if (each === void 0) throw new Error(`${group[0].name}: the run has no image ${name} of Istio`);
|
|
2977
|
+
await cluster.load(each);
|
|
2978
|
+
}
|
|
2800
2979
|
const began = Date.now();
|
|
2801
2980
|
await installMesh(cluster, mesh.flavor, images.mesh);
|
|
2802
2981
|
options.log(` Istio ${mesh.flavor} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);
|
|
@@ -2835,7 +3014,8 @@ const runKindCells = async (cells, options) => {
|
|
|
2835
3014
|
version: 1,
|
|
2836
3015
|
probed: "",
|
|
2837
3016
|
listened: "",
|
|
2838
|
-
expected: cell.pod.replicas
|
|
3017
|
+
expected: cell.pod.replicas,
|
|
3018
|
+
pids: /* @__PURE__ */ new Map()
|
|
2839
3019
|
};
|
|
2840
3020
|
const share = Math.ceil(cell.runs / environments) - (env < cell.runs % environments || cell.runs % environments === 0 ? 0 : 1);
|
|
2841
3021
|
const runs = [await runOnce(ctx, env, -1, true, ours)];
|
|
@@ -2854,10 +3034,7 @@ const runKindCells = async (cells, options) => {
|
|
|
2854
3034
|
lab: image,
|
|
2855
3035
|
redis: redisImage,
|
|
2856
3036
|
...stackOf(cell) === "grpc" ? { grpc: grpcImage } : {},
|
|
2857
|
-
...cell.mesh === void 0 ? {} : {
|
|
2858
|
-
"istio-pilot": ISTIO_IMAGES.pilot,
|
|
2859
|
-
"istio-proxy": ISTIO_IMAGES.proxy
|
|
2860
|
-
}
|
|
3037
|
+
...cell.mesh === void 0 ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]]))
|
|
2861
3038
|
}
|
|
2862
3039
|
});
|
|
2863
3040
|
entry.loads.push(...loads);
|
|
@@ -2954,6 +3131,6 @@ const shardOf = (cells, index, count) => {
|
|
|
2954
3131
|
return out;
|
|
2955
3132
|
};
|
|
2956
3133
|
//#endregion
|
|
2957
|
-
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, 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, 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, stackOf, stageLabImage, stallRemovalBand, stoppingOf, tarEntry, timeSpread, toolPath, transitionOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, writeResult };
|
|
3134
|
+
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, stackOf, stageLabImage, stallRemovalBand, stoppingOf, tarEntry, timeSpread, toolPath, transitionOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, writeResult };
|
|
2958
3135
|
|
|
2959
3136
|
//# sourceMappingURL=index.js.map
|