@mark1russell7/kind 0.1.7 → 0.2.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 +18 -1
- package/build/index.d.ts +26 -6
- package/build/index.js +140 -43
- package/build/index.js.map +1 -1
- package/build/{judge-BkaQ5zGz.d.ts → judge-D5SBTJ-V.d.ts} +49 -8
- package/build/{judge-C1ssYP7m.js → judge-DlmmZHcI.js} +55 -10
- package/build/judge-DlmmZHcI.js.map +1 -0
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/image/Dockerfile +6 -6
- package/image/client-grpc.mjs +97 -0
- package/image/npm-lock.json +326 -0
- package/image/package.json +1 -0
- package/package.json +4 -3
- package/build/judge-C1ssYP7m.js.map +0 -1
package/README.md
CHANGED
|
@@ -89,6 +89,20 @@ Each pod of a cluster shares the kernel of the Docker VM. Thus each stamp of the
|
|
|
89
89
|
|
|
90
90
|
Before each run, the lab deletes the conntrack entries in SYN_SENT to the ClusterIP on each node (`forgetHandshakes`). A note of the run gives each count. Such an entry is a SYN with no answer, and it keeps its NAT for 120 s. A new SYN from the same port goes where the first one went, and it gets no answer either. The clients send requests between the runs too. Thus the restore of one run left such entries, and a client that took the port again in the next run timed out (T07, 2026-10-17).
|
|
91
91
|
|
|
92
|
+
## The stacks
|
|
93
|
+
|
|
94
|
+
From 0.2.0, a cell names its stack (`stack`): its server and its clients. A cell without a stack has the service.
|
|
95
|
+
|
|
96
|
+
- **`service`** is the service of this repository with its declaration, and the HTTP clients. Each request opens a new connection, thus kube-proxy picks a backend for each request.
|
|
97
|
+
- **`grpc`** is the server of grpc-go v1.84.0 from the image of the harness, and the gRPC clients of the lab image. A client holds one channel. Thus kube-proxy picks a backend only when grpc-js connects again: after a GOAWAY of the server, or after the end of the socket.
|
|
98
|
+
- The probes are the gRPC probes of the kubelet on the port of the server.
|
|
99
|
+
- The lines of grpc-go name their pod and stamp in milliseconds (`--name`, `--clock ms`). Its line `stopping` gives the intake and the SIGTERM of a pod (`stoppingOf`): then GracefulStop closes the listener and sends GOAWAY.
|
|
100
|
+
- The clients start after the server is ready. Each failed connect of a channel makes the backoff of grpc-js longer, up to 120 s.
|
|
101
|
+
- The target of a run is the replica that answers the most clients. A channel stays on its replica, and the channels go to the replica that stays after a run. Thus a target by name has no client in most runs.
|
|
102
|
+
- The cell has no declaration (`declaration` is null), thus `describeCell` refuses it. The server grpc-go has no seam for `liveness-restart`, thus its manifest refuses that trigger.
|
|
103
|
+
|
|
104
|
+
A declaration that can be null changes the type `KindCell`. That is a breaking change before 1.0.0, thus the version is 0.2.0.
|
|
105
|
+
|
|
92
106
|
## The image
|
|
93
107
|
|
|
94
108
|
The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`, its clients, the agent and the watcher, on Node 24.21.0. Its base is pinned by digest, and `npm-lock.json` pins the packages from npm. `stageLabImage()` stages its context, and `labImage()` tags the image by its content.
|
|
@@ -101,6 +115,7 @@ The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`
|
|
|
101
115
|
- **The knobs of the listener.** `server_idle_close` is 6 s, the real idle close of Node 24.21: `keepAliveTimeout` of 5 s and its buffer of 1 s. The sweep of idle connections comes each second after the stop of the intake.
|
|
102
116
|
- **The seams** of the service are a flag file that fails its liveness check, and SIGUSR2. SIGUSR2 blocks its event loop for the time in a file.
|
|
103
117
|
- **The clients** (`client.mjs`) send requests on new connections to the ClusterIP. Each request gives one line with its outcome: ok, 503, refused, reset, timeout or unreachable. From 0.1.3, each line also gives the local port of its connection (`lp`), a refused one's too. A conntrack entry is per port. Thus the requests on the port of a failure show the connection before it, and the pod that it went to.
|
|
118
|
+
- **The gRPC clients** (`client-grpc.mjs`) hold one channel of grpc-js 1.14.6 to the ClusterIP, and send a call of `grpc.health.v1.Health/Check` each interval on it. Each call gives the line of a request of `client.mjs`, with the pod that answered (the header `x-pod`). A call that fails at once names the errno of the last connect: refused, reset or unreachable. A call of grpc-js has no deadline at its defaults. Thus a call with no answer in the timeout is a timeout, and the client cancels it.
|
|
104
119
|
|
|
105
120
|
## The triggers
|
|
106
121
|
|
|
@@ -111,7 +126,7 @@ The lab image (`image/`) holds the service of a cell on `@mark1russell7/service`
|
|
|
111
126
|
| `pod-deletion` | `kubectl delete pod` of the first replica |
|
|
112
127
|
| `rolling-update` | a new value of an environment variable of the Deployment |
|
|
113
128
|
| `node-drain` | `kubectl drain` of the node of the first replica, then `uncordon` |
|
|
114
|
-
| `scale-in` | one replica less, then back |
|
|
129
|
+
| `scale-in` | one replica less, then back. For the stack `grpc`, the first replica first gets a deletion cost of -1, thus its ReplicaSet removes it |
|
|
115
130
|
| `crash` | SIGKILL of the process of the service, from the node |
|
|
116
131
|
| `liveness-restart` | the flag file that fails the liveness check of the first replica |
|
|
117
132
|
| `freeze-process` | SIGSTOP and SIGCONT: of kube-proxy on one node across a deletion (T07), or of Redis (T18) |
|
|
@@ -137,6 +152,8 @@ The plan's `event:clock-jump` has no trigger. A step of CLOCK_REALTIME in the VM
|
|
|
137
152
|
|
|
138
153
|
`verdict.ts` judges each run, and each cell on its counted runs. Each time lands in its band ± 100 ms: the poll of the rules and the delay of the watcher. A median in a band gets the test of the harness: its order-statistic interval in the band, made 5 % wider.
|
|
139
154
|
|
|
155
|
+
From 0.1.8, a run can agree with the law but be past its calibrated band (D-EXTEND). A request failed after the window, but before the rules of its node dropped the target. The check of that run tells it (`tail`), and its cell is `extend`. Such a cell does not hold. A request after the drop is still late, thus its run fails the cell.
|
|
156
|
+
|
|
140
157
|
## Tests
|
|
141
158
|
|
|
142
159
|
`pnpm --filter @mark1russell7/kind test` starts the tests of the configuration, the tools, the parser of the rules and the fault mapping. It also starts the tests of the predictions, the verdict and the judge of the real records. They do not need Docker.
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as RulesLine, $t as
|
|
1
|
+
import { $ as RulesLine, $t as KubeProxyMode, A as TriggerV1, At as bandOf, B as RunFacts, Bt as Prediction, C as KindCellResult, Ct as transitionOf, D as PredictionV1, Dt as PORT, E as LoadSample, Et as LOG_LABEL, F as KindDecision, Ft as Declaration, G as referenceOf, Gt as Stack, H as factsOf, Ht as Reference, I as KindVerdict, It as KindCell, J as Endpoint, Jt as TriggerKind, K as reusesReset, Kt as Template, L as LAST_SLACK, Lt as KindRun, M as upgrade, Mt as secondsOf, N as upgradeCell, Nt as showBand, O as RunCheckV1, Ot as REDIS, P as KIND_NOISE, Pt as Band, Q as Request, Qt as KindScenario, R as Propagated, Rt as KubeletProbe, S as KindCellRecordV1, St as stoppingOf, T as KindVerdictV1, Tt as HEALTH_PORT, U as judgeKind, Ut as Replica, V as checkRun, Vt as RedisDeclaration, W as propagationOf, Wt as ResetReuse, X as PodLine, Xt as KUBE_PROXY_MODES, Y as Line, Yt as Workload, Z as RecordLine, Zt as KindConfig, _ as ClusterManifest, _t as signalOf, a as STAGES, an as kindConfig, at as dropOf, b as FORMAT_V1, bt as sliceEmptyOf, c as readRecord, cn as scenario, ct as intakeOf, d as calibrationOf, dt as podsAt, en as NODE_IMAGES, et as SliceLine, f as cellsTable, ft as podsOf, g as BandV1, gt as rulesOf, h as timeSpread, ht as restartsAt, i as quantileOf, in as goDuration, it as deletionOf, j as judge, jt as seconds, k as RunFactsV1, kt as SERVER, l as Judged, ln as scenarioProblems, lt as kernelCounts, m as predictionText, mt as requestsOf, n as medianInterval, nn as cidrOverlap, nt as byClients, o as kindCellRecordSchema, on as kubeProxyPatch, ot as emptyOf, p as medianText, pt as recordsOf, q as unitOf, qt as Trigger, r as middle, rn as cidrRange, rt as byNode, s as kindCellRecordV1Schema, sn as nodeNames, st as failuresOf, t as RELATIVE, tn as NodeImage, tt as backendsAt, u as TimeSpread, ut as outcomesOf, v as EnvironmentManifest, vt as signalsOf, w as KindCellV1, wt as CLIENT, x as KindCellRecord, xt as slicesOf, y as FORMAT, yt as sliceDropOf, z as RunCheck, zt as PodSettings } from "./judge-D5SBTJ-V.js";
|
|
2
2
|
import { Quantity } from "@mark1russell7/quantity";
|
|
3
3
|
import { CheckFn, Finding, HealthServer, Poll, Rule, Scenario, ServiceBuilder, ServiceDescription, Stage, Timing, TransportImpl, Verdict } from "@mark1russell7/service";
|
|
4
4
|
import { RedisClient } from "@mark1russell7/service/redis";
|
|
@@ -205,17 +205,37 @@ export declare const openKindLab: (cluster: Cluster, image: string) => Promise<K
|
|
|
205
205
|
export declare const localPin: (image: string) => Promise<string>;
|
|
206
206
|
//#endregion
|
|
207
207
|
//#region src/manifests.d.ts
|
|
208
|
+
/**
|
|
209
|
+
* This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the
|
|
210
|
+
* server as a Deployment behind a Service, and one client on each node. The server is the service
|
|
211
|
+
* or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout
|
|
212
|
+
* strategy and the probe fields state the defaults of Kubernetes as values.
|
|
213
|
+
*/
|
|
214
|
+
/** This function gives the stack of a cell: the service, when the cell names no stack. */
|
|
215
|
+
export declare const stackOf: (cell: Pick<KindCell, "stack">) => Stack;
|
|
208
216
|
/** This image is Redis 7.4, the stock target of the harness, pinned by digest. */
|
|
209
217
|
export declare const REDIS_IMAGE = "redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f";
|
|
210
218
|
/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */
|
|
211
219
|
export declare const namespaceOf: (cell: Pick<KindCell, "name">) => string;
|
|
212
|
-
/**
|
|
213
|
-
|
|
220
|
+
/**
|
|
221
|
+
* This function gives a probe of the pod, with each field of the cell. The probe of the service is an
|
|
222
|
+
* HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the
|
|
223
|
+
* kubelet. It is a Check of the service "" on the port of the server, and the answer is SERVING until
|
|
224
|
+
* GracefulStop.
|
|
225
|
+
*/
|
|
226
|
+
export declare const probeOf: (path: string | null, probe: KubeletProbe) => Record<string, unknown>;
|
|
214
227
|
/** These are the objects of Redis: one replica behind a Service, with no persistence. */
|
|
215
228
|
export declare const redisObjects: (namespace: string, image: string) => unknown[];
|
|
216
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* These are the objects of the server: a Service of type ClusterIP, and the Deployment with the
|
|
231
|
+
* settings of the cell. The image is the lab image for the service, or the image of grpc-go.
|
|
232
|
+
*/
|
|
217
233
|
export declare const serverObjects: (cell: KindCell, namespace: string, image: string, version?: number) => unknown[];
|
|
218
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* These are the objects of the clients: a DaemonSet with one client on each node, the control plane
|
|
236
|
+
* too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.
|
|
237
|
+
* Each one is in the lab image, and each one writes the same lines.
|
|
238
|
+
*/
|
|
219
239
|
export declare const clientObjects: (cell: KindCell, namespace: string, image: string, target: string) => unknown[];
|
|
220
240
|
//#endregion
|
|
221
241
|
//#region src/predict.d.ts
|
|
@@ -395,5 +415,5 @@ export declare const toolPath: (tool: Tool, key?: string, cache?: string) => str
|
|
|
395
415
|
*/
|
|
396
416
|
export declare const ensureTool: (tool: Tool, key?: string, cache?: string) => Promise<string>;
|
|
397
417
|
//#endregion
|
|
398
|
-
export { type Band, type BandV1, CLIENT, type Cluster, type ClusterManifest, type Credentials, type Declaration, type DeletionWindow, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type FaultPlan, HEALTH_PORT, type Judged, KIND_NOISE, KUBE_PROXY_MODES, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindConfig, type KindDecision, type KindLab, type KindRun, type KindScenario, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, NODE_IMAGES, type NodeImage, PORT, type Parts, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, type Propagation, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, type RunOptions, SERVER, STAGES, type ServiceRule, SliceLine, type Stamp, type Step, type Target, type Template, type TimeSpread, type Tool, type Trigger, type TriggerKind, type TriggerV1, type Verdicts, type Workload, backendsAt, bandOf, byNode, calibrationOf, cellsTable, checkRun, cidrOverlap, cidrRange, deletionOf, dropOf, emptyOf, factsOf, failuresOf, goDuration, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, medianInterval, medianText, middle, nodeNames, outcomesOf, podsAt, podsOf, predictionText, propagationOf, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, scenario, scenarioProblems, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, timeSpread, transitionOf, unitOf, upgrade, upgradeCell };
|
|
418
|
+
export { type Band, type BandV1, CLIENT, type Cluster, type ClusterManifest, type Credentials, type Declaration, type DeletionWindow, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type FaultPlan, HEALTH_PORT, type Judged, KIND_NOISE, KUBE_PROXY_MODES, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindConfig, type KindDecision, type KindLab, type KindRun, type KindScenario, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, NODE_IMAGES, type NodeImage, PORT, type Parts, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, type Propagation, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, type RunOptions, SERVER, STAGES, type ServiceRule, SliceLine, type Stack, type Stamp, type Step, type Target, type Template, type TimeSpread, type Tool, type Trigger, type TriggerKind, type TriggerV1, type Verdicts, type Workload, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, cidrOverlap, cidrRange, deletionOf, dropOf, emptyOf, factsOf, failuresOf, goDuration, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, medianInterval, medianText, middle, nodeNames, outcomesOf, podsAt, podsOf, predictionText, propagationOf, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartsAt, reusesReset, rulesOf, scenario, scenarioProblems, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stoppingOf, timeSpread, transitionOf, unitOf, upgrade, upgradeCell };
|
|
399
419
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as LOG_LABEL, A as byNode, B as recordsOf, C as unitOf, D as quantileOf, E as middle, F as intakeOf, G as signalsOf, H as restartsAt, I as kernelCounts, J as slicesOf, K as sliceDropOf, L as outcomesOf, M as dropOf, N as emptyOf, O as backendsAt, P as failuresOf, Q as HEALTH_PORT, R as podsAt, S as reusesReset, T as medianInterval, U as rulesOf, V as requestsOf, W as signalOf, X as transitionOf, Y as stoppingOf, Z as CLIENT, _ as checkRun, a as calibrationOf, at as secondsOf, b as propagationOf, c as predictionText, d as FORMAT_V1, et as PORT, f as judge, g as LAST_SLACK, h as KIND_NOISE, i as readRecord, it as seconds, j as deletionOf, k as byClients, l as timeSpread, m as upgradeCell, n as kindCellRecordSchema, nt as SERVER, o as cellsTable, ot as showBand, p as upgrade, q as sliceEmptyOf, r as kindCellRecordV1Schema, rt as bandOf, s as medianText, t as STAGES, tt as REDIS, u as FORMAT, v as factsOf, w as RELATIVE, x as referenceOf, y as judgeKind, z as podsOf } from "./judge-DlmmZHcI.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";
|
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import { failSpan, lint, service } from "@mark1russell7/service";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
|
-
import { harnessImage } from "@mark1russell7/harness";
|
|
12
|
+
import { harnessImage, imageOf } from "@mark1russell7/harness";
|
|
13
13
|
import { redisClient } from "@mark1russell7/service/redis";
|
|
14
14
|
//#region src/backends.ts
|
|
15
15
|
const hexIp = (hex) => [
|
|
@@ -520,6 +520,8 @@ const POD = "{pod}";
|
|
|
520
520
|
const NODE = "{node}";
|
|
521
521
|
/** This value marks the next version of the Deployment, for a rollout. */
|
|
522
522
|
const VERSION = "{version}";
|
|
523
|
+
/** This annotation of a pod is its deletion cost for its ReplicaSet: a lower cost goes first at a scale-in. */
|
|
524
|
+
const DELETION_COST = "controller.kubernetes.io/pod-deletion-cost";
|
|
523
525
|
/** This function gives the fault plan of a cell. */
|
|
524
526
|
const planOf = (cell) => {
|
|
525
527
|
const ns = ["-n", "{namespace}"];
|
|
@@ -577,7 +579,17 @@ const planOf = (cell) => {
|
|
|
577
579
|
}]
|
|
578
580
|
};
|
|
579
581
|
case "scale-in": return {
|
|
580
|
-
inject: [{
|
|
582
|
+
inject: [...cell.stack === "grpc" ? [{
|
|
583
|
+
do: "kubectl",
|
|
584
|
+
args: [
|
|
585
|
+
...ns,
|
|
586
|
+
"annotate",
|
|
587
|
+
"pod",
|
|
588
|
+
POD,
|
|
589
|
+
`${DELETION_COST}=-1`,
|
|
590
|
+
"--overwrite"
|
|
591
|
+
]
|
|
592
|
+
}] : [], {
|
|
581
593
|
do: "kubectl",
|
|
582
594
|
args: [
|
|
583
595
|
...ns,
|
|
@@ -800,6 +812,7 @@ const FILES = [
|
|
|
800
812
|
["npm-lock.json", "image/npm-lock.json"],
|
|
801
813
|
["app.mjs", "image/app.mjs"],
|
|
802
814
|
["client.mjs", "image/client.mjs"],
|
|
815
|
+
["client-grpc.mjs", "image/client-grpc.mjs"],
|
|
803
816
|
["agent.mjs", "image/agent.mjs"],
|
|
804
817
|
["watch.mjs", "image/watch.mjs"],
|
|
805
818
|
["compose.ts", "src/compose.ts"],
|
|
@@ -1220,6 +1233,14 @@ const localPin = async (image) => {
|
|
|
1220
1233
|
};
|
|
1221
1234
|
//#endregion
|
|
1222
1235
|
//#region src/manifests.ts
|
|
1236
|
+
/**
|
|
1237
|
+
* This module generates the Kubernetes objects of a cell. They are its namespace, its Redis, the
|
|
1238
|
+
* server as a Deployment behind a Service, and one client on each node. The server is the service
|
|
1239
|
+
* or grpc-go, by the stack of the cell. Each setting of the pod comes from the cell. The rollout
|
|
1240
|
+
* strategy and the probe fields state the defaults of Kubernetes as values.
|
|
1241
|
+
*/
|
|
1242
|
+
/** This function gives the stack of a cell: the service, when the cell names no stack. */
|
|
1243
|
+
const stackOf = (cell) => cell.stack ?? "service";
|
|
1223
1244
|
/** This image is Redis 7.4, the stock target of the harness, pinned by digest. */
|
|
1224
1245
|
const REDIS_IMAGE = "redis:7.4@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f";
|
|
1225
1246
|
const whole = (amount, what) => {
|
|
@@ -1229,18 +1250,28 @@ const whole = (amount, what) => {
|
|
|
1229
1250
|
};
|
|
1230
1251
|
/** This function gives the namespace of a cell: `lab-` and 10 digits of the sha256 of its name. */
|
|
1231
1252
|
const namespaceOf = (cell) => `lab-${createHash("sha256").update(cell.name).digest("hex").slice(0, 10)}`;
|
|
1232
|
-
/**
|
|
1253
|
+
/**
|
|
1254
|
+
* This function gives a probe of the pod, with each field of the cell. The probe of the service is an
|
|
1255
|
+
* HTTP GET of its health server (`path`). The probe of grpc-go (`path` null) is the gRPC probe of the
|
|
1256
|
+
* kubelet. It is a Check of the service "" on the port of the server, and the answer is SERVING until
|
|
1257
|
+
* GracefulStop.
|
|
1258
|
+
*/
|
|
1233
1259
|
const probeOf = (path, probe) => ({
|
|
1234
|
-
httpGet: {
|
|
1260
|
+
...path === null ? { grpc: { port: PORT } } : { httpGet: {
|
|
1235
1261
|
path,
|
|
1236
1262
|
port: HEALTH_PORT
|
|
1237
|
-
},
|
|
1263
|
+
} },
|
|
1238
1264
|
initialDelaySeconds: 0,
|
|
1239
|
-
periodSeconds: whole(probe.period, `the period of ${path}`),
|
|
1240
|
-
timeoutSeconds: whole(probe.timeout, `the timeout of ${path}`),
|
|
1265
|
+
periodSeconds: whole(probe.period, `the period of ${path ?? "the gRPC probe"}`),
|
|
1266
|
+
timeoutSeconds: whole(probe.timeout, `the timeout of ${path ?? "the gRPC probe"}`),
|
|
1241
1267
|
failureThreshold: probe.threshold,
|
|
1242
1268
|
successThreshold: 1
|
|
1243
1269
|
});
|
|
1270
|
+
/** This variable is the name of the pod, from the API: the service and grpc-go put it in their lines. */
|
|
1271
|
+
const POD_ENV = {
|
|
1272
|
+
name: "POD_NAME",
|
|
1273
|
+
valueFrom: { fieldRef: { fieldPath: "metadata.name" } }
|
|
1274
|
+
};
|
|
1244
1275
|
/** This function gives the environment of the app: its config as JSON, and the name of its pod. */
|
|
1245
1276
|
const appEnv = (cell) => [{
|
|
1246
1277
|
name: "CONFIG",
|
|
@@ -1250,10 +1281,44 @@ const appEnv = (cell) => [{
|
|
|
1250
1281
|
healthPort: HEALTH_PORT,
|
|
1251
1282
|
declaration: cell.declaration
|
|
1252
1283
|
})
|
|
1253
|
-
},
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1284
|
+
}, POD_ENV];
|
|
1285
|
+
/**
|
|
1286
|
+
* This function gives the container of the server of a cell. The service runs `app.mjs` of the lab
|
|
1287
|
+
* image with its declaration. The server grpc-go runs from the image of the harness. Its lines name
|
|
1288
|
+
* the pod and stamp in milliseconds. Its replies name the pod in the header `x-pod`. Kubernetes
|
|
1289
|
+
* expands `$(POD_NAME)` in the command.
|
|
1290
|
+
*/
|
|
1291
|
+
const serverContainer = (cell, version) => {
|
|
1292
|
+
const env = {
|
|
1293
|
+
name: "LAB_VERSION",
|
|
1294
|
+
value: String(version)
|
|
1295
|
+
};
|
|
1296
|
+
if (stackOf(cell) === "service") return {
|
|
1297
|
+
command: ["node", "app.mjs"],
|
|
1298
|
+
env: [...appEnv(cell), env],
|
|
1299
|
+
ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],
|
|
1300
|
+
...cell.pod.startup === null ? {} : { startupProbe: probeOf("/startup", cell.pod.startup) },
|
|
1301
|
+
readinessProbe: probeOf("/readiness", cell.pod.readiness),
|
|
1302
|
+
livenessProbe: probeOf("/liveness", cell.pod.liveness)
|
|
1303
|
+
};
|
|
1304
|
+
if (cell.trigger.kind === "liveness-restart") throw new Error(`${cell.name}: grpc-go has no seam for liveness-restart`);
|
|
1305
|
+
return {
|
|
1306
|
+
command: [
|
|
1307
|
+
"grpc-go-server",
|
|
1308
|
+
"--port",
|
|
1309
|
+
String(PORT),
|
|
1310
|
+
"--name",
|
|
1311
|
+
"$(POD_NAME)",
|
|
1312
|
+
"--clock",
|
|
1313
|
+
"ms"
|
|
1314
|
+
],
|
|
1315
|
+
env: [POD_ENV, env],
|
|
1316
|
+
ports: [{ containerPort: PORT }],
|
|
1317
|
+
...cell.pod.startup === null ? {} : { startupProbe: probeOf(null, cell.pod.startup) },
|
|
1318
|
+
readinessProbe: probeOf(null, cell.pod.readiness),
|
|
1319
|
+
livenessProbe: probeOf(null, cell.pod.liveness)
|
|
1320
|
+
};
|
|
1321
|
+
};
|
|
1257
1322
|
/** These are the objects of Redis: one replica behind a Service, with no persistence. */
|
|
1258
1323
|
const redisObjects = (namespace, image) => [{
|
|
1259
1324
|
apiVersion: "apps/v1",
|
|
@@ -1308,7 +1373,10 @@ const redisObjects = (namespace, image) => [{
|
|
|
1308
1373
|
}]
|
|
1309
1374
|
}
|
|
1310
1375
|
}];
|
|
1311
|
-
/**
|
|
1376
|
+
/**
|
|
1377
|
+
* These are the objects of the server: a Service of type ClusterIP, and the Deployment with the
|
|
1378
|
+
* settings of the cell. The image is the lab image for the service, or the image of grpc-go.
|
|
1379
|
+
*/
|
|
1312
1380
|
const serverObjects = (cell, namespace, image, version = 1) => {
|
|
1313
1381
|
const preStop = whole(cell.pod.preStop, "the preStop sleep");
|
|
1314
1382
|
return [{
|
|
@@ -1321,7 +1389,7 @@ const serverObjects = (cell, namespace, image, version = 1) => {
|
|
|
1321
1389
|
spec: {
|
|
1322
1390
|
selector: { app: "lab" },
|
|
1323
1391
|
ports: [{
|
|
1324
|
-
name: "http",
|
|
1392
|
+
name: stackOf(cell) === "grpc" ? "grpc" : "http",
|
|
1325
1393
|
port: 80,
|
|
1326
1394
|
targetPort: PORT
|
|
1327
1395
|
}]
|
|
@@ -1364,15 +1432,7 @@ const serverObjects = (cell, namespace, image, version = 1) => {
|
|
|
1364
1432
|
name: "app",
|
|
1365
1433
|
image,
|
|
1366
1434
|
imagePullPolicy: "Never",
|
|
1367
|
-
|
|
1368
|
-
env: [...appEnv(cell), {
|
|
1369
|
-
name: "LAB_VERSION",
|
|
1370
|
-
value: String(version)
|
|
1371
|
-
}],
|
|
1372
|
-
ports: [{ containerPort: PORT }, { containerPort: HEALTH_PORT }],
|
|
1373
|
-
...cell.pod.startup === null ? {} : { startupProbe: probeOf("/startup", cell.pod.startup) },
|
|
1374
|
-
readinessProbe: probeOf("/readiness", cell.pod.readiness),
|
|
1375
|
-
livenessProbe: probeOf("/liveness", cell.pod.liveness),
|
|
1435
|
+
...serverContainer(cell, version),
|
|
1376
1436
|
...preStop === 0 ? {} : { lifecycle: { preStop: { sleep: { seconds: preStop } } } }
|
|
1377
1437
|
}]
|
|
1378
1438
|
}
|
|
@@ -1380,7 +1440,11 @@ const serverObjects = (cell, namespace, image, version = 1) => {
|
|
|
1380
1440
|
}
|
|
1381
1441
|
}];
|
|
1382
1442
|
};
|
|
1383
|
-
/**
|
|
1443
|
+
/**
|
|
1444
|
+
* These are the objects of the clients: a DaemonSet with one client on each node, the control plane
|
|
1445
|
+
* too. The client of the service is `client.mjs`, and the client of grpc-go is `client-grpc.mjs`.
|
|
1446
|
+
* Each one is in the lab image, and each one writes the same lines.
|
|
1447
|
+
*/
|
|
1384
1448
|
const clientObjects = (cell, namespace, image, target) => [{
|
|
1385
1449
|
apiVersion: "apps/v1",
|
|
1386
1450
|
kind: "DaemonSet",
|
|
@@ -1407,7 +1471,7 @@ const clientObjects = (cell, namespace, image, target) => [{
|
|
|
1407
1471
|
name: CLIENT,
|
|
1408
1472
|
image,
|
|
1409
1473
|
imagePullPolicy: "Never",
|
|
1410
|
-
command: ["node", "client.mjs"],
|
|
1474
|
+
command: ["node", stackOf(cell) === "grpc" ? "client-grpc.mjs" : "client.mjs"],
|
|
1411
1475
|
env: [
|
|
1412
1476
|
{
|
|
1413
1477
|
name: "TARGET",
|
|
@@ -1694,6 +1758,7 @@ const lintCell = (description, cell, propagation) => lint(description, scenarioO
|
|
|
1694
1758
|
const verdictsOf = (findings) => Object.fromEntries(findings.map((finding) => [finding.rule, finding.verdict]));
|
|
1695
1759
|
/** This function gives the description of the service of a cell, as the app of the image composes it. Nothing connects. */
|
|
1696
1760
|
const describeCell = (cell) => {
|
|
1761
|
+
if (cell.declaration === null) throw new Error(`${cell.name}: the server is not the service, thus the cell has no declaration to describe`);
|
|
1697
1762
|
const redis = cell.declaration.redis;
|
|
1698
1763
|
const client = redis === null ? void 0 : redisClient("redis", {
|
|
1699
1764
|
host: "redis",
|
|
@@ -1713,12 +1778,16 @@ const RULES_EVERY = {
|
|
|
1713
1778
|
};
|
|
1714
1779
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1715
1780
|
const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1781
|
+
/** This function tells if a cell has Redis: only a declaration of the service can have it. */
|
|
1782
|
+
const hasRedis = (cell) => cell.declaration !== null && cell.declaration.redis !== null;
|
|
1716
1783
|
/** This tracker keeps the latest state of the cell from the lines: each pod, each slice, the backends of each node and each client. */
|
|
1717
1784
|
var Tracker = class {
|
|
1718
1785
|
pods = /* @__PURE__ */ new Map();
|
|
1719
1786
|
slices = /* @__PURE__ */ new Map();
|
|
1720
1787
|
backends = /* @__PURE__ */ new Map();
|
|
1721
1788
|
clients = /* @__PURE__ */ new Map();
|
|
1789
|
+
/** This map is the backend of the last answer of each client, by client. */
|
|
1790
|
+
answers = /* @__PURE__ */ new Map();
|
|
1722
1791
|
last = 0;
|
|
1723
1792
|
see = (line) => {
|
|
1724
1793
|
if (typeof line.t === "number" && line.t > this.last && line.ev === "req") this.last = line.t;
|
|
@@ -1740,6 +1809,7 @@ var Tracker = class {
|
|
|
1740
1809
|
if (request.o === "ok") client.ok = Math.max(client.ok, request.t);
|
|
1741
1810
|
else client.failed = Math.max(client.failed, request.t);
|
|
1742
1811
|
this.clients.set(request.p, client);
|
|
1812
|
+
if (request.o === "ok" && request.b !== void 0) this.answers.set(request.p, request.b);
|
|
1743
1813
|
}
|
|
1744
1814
|
};
|
|
1745
1815
|
servers() {
|
|
@@ -1754,7 +1824,7 @@ const unsteady = (ctx) => {
|
|
|
1754
1824
|
const busy = servers.find((pod) => pod.del || !pod.ready);
|
|
1755
1825
|
if (busy !== void 0) return `${busy.name} is not ready`;
|
|
1756
1826
|
if (new Set(servers.map((pod) => pod.labels["pod-template-hash"])).size > 1) return "two versions of the replicas";
|
|
1757
|
-
if (cell
|
|
1827
|
+
if (hasRedis(cell)) {
|
|
1758
1828
|
const redis = [...tracker.pods.values()].filter((pod) => pod.labels["app"] === REDIS);
|
|
1759
1829
|
if (redis.length !== 1 || !redis[0].ready || redis[0].del) return "Redis is not ready";
|
|
1760
1830
|
}
|
|
@@ -1819,16 +1889,20 @@ const quiet = async (ctx, ours) => {
|
|
|
1819
1889
|
await sleep(3e4);
|
|
1820
1890
|
}
|
|
1821
1891
|
};
|
|
1822
|
-
/**
|
|
1823
|
-
|
|
1892
|
+
/**
|
|
1893
|
+
* This function deploys a cell in its namespace, with the watcher, the poll of the rules and the
|
|
1894
|
+
* clients. A client of gRPC starts after the server is ready: each failed connect of a channel makes
|
|
1895
|
+
* the backoff of grpc-js longer, up to 120 s.
|
|
1896
|
+
*/
|
|
1897
|
+
const deploy = async (lab, cluster, cell, namespace, images) => {
|
|
1824
1898
|
await cluster.apply([{
|
|
1825
1899
|
apiVersion: "v1",
|
|
1826
1900
|
kind: "Namespace",
|
|
1827
1901
|
metadata: { name: namespace }
|
|
1828
1902
|
}]);
|
|
1829
1903
|
await lab.watch([namespace]);
|
|
1830
|
-
if (cell
|
|
1831
|
-
await cluster.apply(redisObjects(namespace,
|
|
1904
|
+
if (hasRedis(cell)) {
|
|
1905
|
+
await cluster.apply(redisObjects(namespace, images.redis));
|
|
1832
1906
|
await cluster.kubectl([
|
|
1833
1907
|
"-n",
|
|
1834
1908
|
namespace,
|
|
@@ -1838,7 +1912,9 @@ const deploy = async (lab, cluster, cell, namespace, redisImage) => {
|
|
|
1838
1912
|
"--timeout=180s"
|
|
1839
1913
|
]);
|
|
1840
1914
|
}
|
|
1841
|
-
|
|
1915
|
+
const grpc = stackOf(cell) === "grpc";
|
|
1916
|
+
if (grpc && images.grpc === void 0) throw new Error(`${cell.name}: the cluster has no image of grpc-go`);
|
|
1917
|
+
await cluster.apply(serverObjects(cell, namespace, grpc ? images.grpc : lab.image));
|
|
1842
1918
|
const clusterIp = (await cluster.kubectl([
|
|
1843
1919
|
"-n",
|
|
1844
1920
|
namespace,
|
|
@@ -1848,7 +1924,8 @@ const deploy = async (lab, cluster, cell, namespace, redisImage) => {
|
|
|
1848
1924
|
"-o",
|
|
1849
1925
|
"jsonpath={.spec.clusterIP}"
|
|
1850
1926
|
])).trim();
|
|
1851
|
-
|
|
1927
|
+
const clients = clientObjects(cell, namespace, lab.image, `${clusterIp}:80`);
|
|
1928
|
+
if (!grpc) await cluster.apply(clients);
|
|
1852
1929
|
await cluster.kubectl([
|
|
1853
1930
|
"-n",
|
|
1854
1931
|
namespace,
|
|
@@ -1857,6 +1934,7 @@ const deploy = async (lab, cluster, cell, namespace, redisImage) => {
|
|
|
1857
1934
|
`deployment/lab`,
|
|
1858
1935
|
"--timeout=300s"
|
|
1859
1936
|
]);
|
|
1937
|
+
if (grpc) await cluster.apply(clients);
|
|
1860
1938
|
await cluster.kubectl([
|
|
1861
1939
|
"-n",
|
|
1862
1940
|
namespace,
|
|
@@ -1889,13 +1967,21 @@ const probeThePods = async (ctx) => {
|
|
|
1889
1967
|
await ctx.lab.probe([...new Set(netns)]);
|
|
1890
1968
|
ctx.probed = key;
|
|
1891
1969
|
};
|
|
1892
|
-
/**
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1970
|
+
/**
|
|
1971
|
+
* This function gives the replicas of the server at this time, in the sequence of their names. The
|
|
1972
|
+
* first one is the target of a run. A client of gRPC holds its connection to one replica, and the
|
|
1973
|
+
* channels go to the replica that stays after a run. Thus for the stack `grpc`, the first replica
|
|
1974
|
+
* is the one that answers the most clients (`byClients`).
|
|
1975
|
+
*/
|
|
1976
|
+
const replicasOf = (ctx) => {
|
|
1977
|
+
const replicas = ctx.tracker.servers().map((pod) => ({
|
|
1978
|
+
pod: pod.name,
|
|
1979
|
+
node: pod.node ?? "",
|
|
1980
|
+
ip: pod.ip ?? "",
|
|
1981
|
+
version: pod.labels["pod-template-hash"] ?? ""
|
|
1982
|
+
}));
|
|
1983
|
+
return stackOf(ctx.cell) === "grpc" ? byClients(replicas, ctx.tracker.answers) : replicas;
|
|
1984
|
+
};
|
|
1899
1985
|
/** This function does one step of a fault plan. It gives the stamp of its act, or null for an act without a stamp. */
|
|
1900
1986
|
const doStep = async (ctx, step, replicas, background) => {
|
|
1901
1987
|
const { lab, cluster, namespace } = ctx;
|
|
@@ -2063,7 +2149,7 @@ const runOnce = async (ctx, env, index, warmup, ours) => {
|
|
|
2063
2149
|
if (await steady(ctx, 3e5) !== null) return empty("the cell was not at rest after the wait for a quiet VM", await lab.now());
|
|
2064
2150
|
const replicas = replicasOf(ctx);
|
|
2065
2151
|
const restarts = new Map(ctx.tracker.servers().map((pod) => [pod.name, pod.cs.reduce((sum, status) => sum + status.rc, 0)]));
|
|
2066
|
-
const target = cell
|
|
2152
|
+
const target = hasRedis(cell) && [
|
|
2067
2153
|
"rst",
|
|
2068
2154
|
"blackhole",
|
|
2069
2155
|
"freeze-process",
|
|
@@ -2206,6 +2292,15 @@ const runKindCells = async (cells, options) => {
|
|
|
2206
2292
|
const environments = options.environments ?? 2;
|
|
2207
2293
|
const image = await labImage();
|
|
2208
2294
|
const redisImage = await localPin(REDIS_IMAGE);
|
|
2295
|
+
const grpcImage = cells.some((cell) => stackOf(cell) === "grpc") ? await imageOf({
|
|
2296
|
+
kind: "shipped",
|
|
2297
|
+
name: "grpc-go"
|
|
2298
|
+
}) : void 0;
|
|
2299
|
+
const images = {
|
|
2300
|
+
lab: image,
|
|
2301
|
+
redis: redisImage,
|
|
2302
|
+
grpc: grpcImage
|
|
2303
|
+
};
|
|
2209
2304
|
const groups = /* @__PURE__ */ new Map();
|
|
2210
2305
|
for (const cell of cells) groups.set(`${cell.mode}-${cell.workers}`, [...groups.get(`${cell.mode}-${cell.workers}`) ?? [], cell]);
|
|
2211
2306
|
const results = [];
|
|
@@ -2221,6 +2316,7 @@ const runKindCells = async (cells, options) => {
|
|
|
2221
2316
|
try {
|
|
2222
2317
|
await cluster.load(image);
|
|
2223
2318
|
await cluster.load(redisImage);
|
|
2319
|
+
if (grpcImage !== void 0 && group.some((cell) => stackOf(cell) === "grpc")) await cluster.load(grpcImage);
|
|
2224
2320
|
lab = await openKindLab(cluster, image);
|
|
2225
2321
|
const { GenericContainer } = await import("testcontainers");
|
|
2226
2322
|
const Shared = class extends GenericContainer {
|
|
@@ -2239,7 +2335,7 @@ const runKindCells = async (cells, options) => {
|
|
|
2239
2335
|
const off = lab.listen(tracker.see);
|
|
2240
2336
|
const loads = [];
|
|
2241
2337
|
try {
|
|
2242
|
-
const clusterIp = await deploy(lab, cluster, cell, namespace,
|
|
2338
|
+
const clusterIp = await deploy(lab, cluster, cell, namespace, images);
|
|
2243
2339
|
for (const name of [...lab.own(), loader.getName().replace(/^\//, "")]) ours.add(name);
|
|
2244
2340
|
const ctx = {
|
|
2245
2341
|
lab,
|
|
@@ -2271,7 +2367,8 @@ const runKindCells = async (cells, options) => {
|
|
|
2271
2367
|
cluster: manifest,
|
|
2272
2368
|
images: {
|
|
2273
2369
|
lab: image,
|
|
2274
|
-
redis: redisImage
|
|
2370
|
+
redis: redisImage,
|
|
2371
|
+
...stackOf(cell) === "grpc" ? { grpc: grpcImage } : {}
|
|
2275
2372
|
}
|
|
2276
2373
|
});
|
|
2277
2374
|
entry.loads.push(...loads);
|
|
@@ -2360,6 +2457,6 @@ const shardOf = (cells, index, count) => {
|
|
|
2360
2457
|
return out;
|
|
2361
2458
|
};
|
|
2362
2459
|
//#endregion
|
|
2363
|
-
export { CLIENT, CONTROLLER, DOCUMENTED, DRAIN_RULES, FLAG_CHECK, FORMAT, FORMAT_V1, HEALTH_PORT, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LOG_LABEL, NODE_IMAGES, O04, O09, O10, O16, PORT, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, STAGES, WATCHDOG, backendsAt, backendsOf, bandOf, byNode, cacheFolder, calibrationOf, capOf, cellsTable, checkRun, cidrOverlap, cidrRange, clientObjects, compose, createCluster, deletes, deletionOf, deletionWindow, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, goDuration, intakeAfter, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, latchBand, lintCell, localPin, medianInterval, medianText, middle, namespaceOf, nodeNames, openKindLab, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, predictionText, probeOf, propagationOf, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverObjects, sha256, shardOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stageLabImage, stallRemovalBand, timeSpread, toolPath, transitionOf, unitOf, upgrade, upgradeCell, verdictsOf, writeResult };
|
|
2460
|
+
export { CLIENT, CONTROLLER, DOCUMENTED, DRAIN_RULES, FLAG_CHECK, FORMAT, FORMAT_V1, HEALTH_PORT, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LOG_LABEL, NODE_IMAGES, O04, O09, O10, O16, PORT, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, STAGES, WATCHDOG, backendsAt, backendsOf, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, checkRun, cidrOverlap, cidrRange, clientObjects, compose, createCluster, deletes, deletionOf, deletionWindow, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, goDuration, intakeAfter, intakeOf, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, latchBand, lintCell, localPin, medianInterval, medianText, middle, namespaceOf, nodeNames, openKindLab, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, predictionText, probeOf, propagationOf, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverObjects, sha256, shardOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, slicesOf, stackOf, stageLabImage, stallRemovalBand, stoppingOf, timeSpread, toolPath, transitionOf, unitOf, upgrade, upgradeCell, verdictsOf, writeResult };
|
|
2364
2461
|
|
|
2365
2462
|
//# sourceMappingURL=index.js.map
|