@mark1russell7/harness 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/build/index.d.ts +13 -0
- package/build/index.js +12 -6
- package/build/index.js.map +1 -1
- package/image/procedures/Dockerfile +3 -2
- package/image/procedures/client-ws.mjs +37 -12
- package/image/procedures/npm-lock.json +13 -16
- package/image/procedures/package.json +1 -1
- package/image/procedures/server-ws.mjs +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,10 +107,11 @@ A fault names a member by its role at the start of a run: `primary`, or `replica
|
|
|
107
107
|
- or by itself, a gap after its first reply (`gap`), for the keep-alive race.
|
|
108
108
|
- **The lines of an HTTP client.** It writes the lines of the TCP client, with the number `i` of the request on each line of a request. It also writes the connection of each try of a request (`request`, then `retry`), with its local address and a flag for a reused connection. Each connection gets a `conn` line, and each end of a connection a line (`eof`, `conn-error`, `closed`). The end of the second request is a `done` line.
|
|
109
109
|
- **A target with a script.** A target can take files before it starts (`Target.files`), for example the script of a Node server on the stock Node image. A target that writes JSON lines (`Target.lines`) gives them to each run (`Run.server`). The manifest holds the sha256 of each file and the first line of the target.
|
|
110
|
-
- **The WebSocket client** (`Cell.client` is `procedures-ws`) is the `WebSocketTransport` of `@procedures/client` 0.
|
|
110
|
+
- **The WebSocket client** (`Cell.client` is `procedures-ws`) is the `WebSocketTransport` of `@procedures/client` 0.7.0. The cell gives each of its settings (`probe.ws`): the class of its sockets, the heartbeat, the reconnect, and the times of a call.
|
|
111
111
|
- Its first request is `harness.echo`. In flight, its request into the fault is `harness.echo`, or `harness.hold` for a held reply.
|
|
112
112
|
- It writes the lines of the TCP client. It also writes a line for each act of the program: `ping`, `drop`, `reconnecting` and `dial`.
|
|
113
|
-
- The class of its sockets is the global `WebSocket` of Node,
|
|
113
|
+
- The class of its sockets is the global `WebSocket` of Node, with the transport of the root entry. Or it is the `WebSocket` of the `ws` package, with the transport of the Node entry `@procedures/client/node`.
|
|
114
|
+
- The transport gets a subclass of that class. The subclass writes a line, then gives the work to its class. Thus the transport, undici and ws operate as they do without it.
|
|
114
115
|
- The subclass has `terminate()` only when its class has it, because the transport drops a socket with `terminate()` when it exists.
|
|
115
116
|
- **A target from an image of the harness** (`Target.harness`). The target operates an image of the harness in place of its stock image. The procedures server is a script in the image of the WebSocket client (`server-ws.mjs`). Thus the client and the server use one release of the package. `Target.image` is the stock base of that image, and the manifest names both.
|
|
116
117
|
- **An agent** (`lab.agent(image, command, { pid })`) is a container in the network namespace of the target. With `pid`, it also shares the process namespace of the target, thus it can signal the server process. Its JSON lines and its `exec` stay available to the caller.
|
|
@@ -133,7 +134,7 @@ The harness builds six images. The tag of each image comes from the content of a
|
|
|
133
134
|
- The events of the driver are `sdam` lines (the heartbeats and the changes of each server), `cmap` lines (the pools) and `command` lines (each try of an operation).
|
|
134
135
|
- Each operation is a `sent` line, then an `ack` line or a `fail` line. An `ack` line has the server that accepted it. A `fail` line has the name and the labels of its error.
|
|
135
136
|
- The same image holds `stepdown-mongo.mjs`, the sender of `replSetStepDown`.
|
|
136
|
-
- **The procedures image** (`image/procedures`) holds `@procedures/client` 0.
|
|
137
|
+
- **The procedures image** (`image/procedures`) holds `@procedures/client` 0.7.0 and `ws` 8.22.0 on Node 24.21.0, whose WebSocket is undici 7.29.1. The digest of the image and an npm lockfile pin them. It holds the WebSocket client (`client-ws.mjs`) and the script of the procedures server (`server-ws.mjs`). Each takes each of its settings as a flag, and it has no default. The lockfile holds one copy of `ws`, and the client stops when `@procedures/client/node` resolves another one.
|
|
137
138
|
- **The router image** (`image/router`) is a digest-pinned Alpine with conntrack-tools, nftables, iproute2 and Python. `mbx.sh` sets the form of the router in one nftables table, `ctwatch.py` writes the conntrack events, and `resetter.py` sends the resets of the `reset-both` form. Each run records its packages in the manifest.
|
|
138
139
|
|
|
139
140
|
The build does the `cargo test` of the two crates. Thus the record decoder passes its tests before an image exists. Each amount that the native programs print is a quantity in a canonical unit. For example, an RTO is in `jiffy`, a round trip in `us`, and a segment in `B`.
|
|
@@ -202,6 +203,8 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
202
203
|
- A drop counts when it comes before each report of the kernel. A report of the kernel that comes first is the detection, and the drop after it is not.
|
|
203
204
|
- The second view is the stamps of the program: from its `ping` line to its `drop` line. The steps are the later pings, each from the reference.
|
|
204
205
|
- The lines of a client name the socket by its local address (`local`), and the lines of a server by its peer (`peer`). For the server, the lines are the lines of the target. A line whose address is null is about a try that did not open.
|
|
206
|
+
- The harness reads the peer of a server from the records of its socket. Behind a router, that peer is the address of the router, not the address of the client.
|
|
207
|
+
- A first ping without an answer can come late, for example at a beat of a server whose phase no document gives. Then the prediction gives its latest time after the reply (`startsBy`), and the run waits that much longer.
|
|
205
208
|
- A prediction can say how the program drops (`drop`). A close frame (`frame`) is a write, and the socket stays ESTABLISHED after it. A close (`close`) closes the socket in δ_abs: a FIN or an abort. Another drop is a finding. A FIN of the peer that answers a close frame is no close by the program. The run gives the time from the `drop` line to the close by the program (`left`).
|
|
206
209
|
- The client process ends at the end of each run, and its kernel then closes each socket that is still open. A prediction can say that the program is that client process (`exits`). Then a close in δ_abs of that end is no close by the program: `left` stays null, and `exited` gives the time.
|
|
207
210
|
- The end of the process is the first change out of ESTABLISHED of another socket of the client after its last `dial` line (`exitOf`). The program dropped none of these sockets.
|
package/build/index.d.ts
CHANGED
|
@@ -456,6 +456,12 @@ type Prediction = {
|
|
|
456
456
|
readonly low: Quantity;
|
|
457
457
|
/** This time is the closed form, plus the most that the timer can fire late, plus each proxy time. */
|
|
458
458
|
readonly high: Quantity;
|
|
459
|
+
/**
|
|
460
|
+
* This time is the latest reference of the band after the reply of the client. It is for a
|
|
461
|
+
* reference that can come late, for example the first ping without an answer of a server
|
|
462
|
+
* heartbeat. The run then waits that much longer for the detection.
|
|
463
|
+
*/
|
|
464
|
+
readonly startsBy?: Quantity;
|
|
459
465
|
readonly rtt?: RttRange;
|
|
460
466
|
readonly through?: readonly ProxyTime[];
|
|
461
467
|
readonly group?: GroupPrediction;
|
|
@@ -798,6 +804,13 @@ interface HopPlace {
|
|
|
798
804
|
type FaultPlace = "target" | "client" | HopPlace | GroupPlace;
|
|
799
805
|
/** This function tells if a fault place is a hop of a topology. */
|
|
800
806
|
export declare const isHopPlace: (place: FaultPlace | undefined) => place is HopPlace;
|
|
807
|
+
/**
|
|
808
|
+
* This function gives the latest end of a band in seconds, from the reply of the client. It is
|
|
809
|
+
* the high bound, after the latest reference of the band (`startsBy`).
|
|
810
|
+
*/
|
|
811
|
+
export declare const latestEndOf: (prediction: Extract<Prediction, {
|
|
812
|
+
readonly detects: true;
|
|
813
|
+
}>) => number;
|
|
801
814
|
/** A middlebox event is one line of the router: a change of a conntrack entry, or a reset that it sent. `t` is CLOCK_MONOTONIC in nanoseconds. */
|
|
802
815
|
interface MiddleboxEvent {
|
|
803
816
|
readonly ev: string;
|
package/build/index.js
CHANGED
|
@@ -8,6 +8,11 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
//#region src/cell.ts
|
|
9
9
|
/** This function tells if a fault place is a hop of a topology. */
|
|
10
10
|
const isHopPlace = (place) => typeof place === "object" && "hop" in place;
|
|
11
|
+
/**
|
|
12
|
+
* This function gives the latest end of a band in seconds, from the reply of the client. It is
|
|
13
|
+
* the high bound, after the latest reference of the band (`startsBy`).
|
|
14
|
+
*/
|
|
15
|
+
const latestEndOf = (prediction) => valueIn(prediction.high, "s") + (prediction.startsBy === void 0 ? 0 : valueIn(prediction.startsBy, "s"));
|
|
11
16
|
//#endregion
|
|
12
17
|
//#region src/verdict.ts
|
|
13
18
|
/** This function gives the words of the outcome of a second request: its connection, a retry, and its end. */
|
|
@@ -1652,8 +1657,9 @@ const measureSocket = (cell, run) => {
|
|
|
1652
1657
|
const closeWait = socket.find((record) => record.ev === "state" && newState(record) === 8);
|
|
1653
1658
|
const own = cell.prediction.detects === true && cell.prediction.errno === "CLOSED" || cell.prediction.detects === null && cell.prediction.ends === "own-close" ? socket.find((record) => record.ev === "state" && oldState(record) === 1 && newState(record) === 4) : void 0;
|
|
1654
1659
|
const lines = programLines(cell, run);
|
|
1660
|
+
const named = server ? socket.find((record) => record.dst !== void 0)?.dst ?? local : local;
|
|
1655
1661
|
const kernelEnded = reported?.mono ?? closeWait?.mono ?? own?.mono ?? seen?.t;
|
|
1656
|
-
const first = dropOf(lines, socket,
|
|
1662
|
+
const first = dropOf(lines, socket, named, noiseOf(cell));
|
|
1657
1663
|
const drop = first !== void 0 && (kernelEnded === void 0 || first.at <= kernelEnded) ? first : void 0;
|
|
1658
1664
|
const dropped = drop !== void 0;
|
|
1659
1665
|
const outcome = dropped ? { kind: "dropped" } : reported !== void 0 ? {
|
|
@@ -1666,7 +1672,7 @@ const measureSocket = (cell, run) => {
|
|
|
1666
1672
|
const ended = drop?.at ?? kernelEnded;
|
|
1667
1673
|
const noticed = reported !== void 0 && seen !== void 0 && !dropped ? between(reported.mono, seen.t) : null;
|
|
1668
1674
|
const { prediction } = cell;
|
|
1669
|
-
const pings = pingsOf(lines, socket,
|
|
1675
|
+
const pings = pingsOf(lines, socket, named, noiseOf(cell));
|
|
1670
1676
|
const before = run.fault?.t1 ?? ready.t;
|
|
1671
1677
|
const referenceAt = (from) => {
|
|
1672
1678
|
if (from === "request-sent") return request.t;
|
|
@@ -1947,7 +1953,7 @@ const MONGO = {
|
|
|
1947
1953
|
dockerfile: "harness/image/mongo/Dockerfile",
|
|
1948
1954
|
from: ["harness/image/mongo"]
|
|
1949
1955
|
};
|
|
1950
|
-
/** This image holds `@procedures/client` 0.
|
|
1956
|
+
/** This image holds `@procedures/client` 0.7.0 on Node 24.21.0: its WebSocket client as a client of the harness, and the script of its server. */
|
|
1951
1957
|
const PROCEDURES = {
|
|
1952
1958
|
name: "loom-harness-procedures",
|
|
1953
1959
|
dockerfile: "harness/image/procedures/Dockerfile",
|
|
@@ -4185,7 +4191,7 @@ const droppedSoFar = (records) => {
|
|
|
4185
4191
|
/** This function gives the argv of the client for a cell, with a deadline far after all that it waits for. */
|
|
4186
4192
|
const probeArgs = (cell, lab) => {
|
|
4187
4193
|
const { prediction, probe } = cell;
|
|
4188
|
-
const longest = (prediction.detects === true && prediction.driver !== void 0 ? longestOf(cell) : prediction.detects ?
|
|
4194
|
+
const longest = (prediction.detects === true && prediction.driver !== void 0 ? longestOf(cell) : prediction.detects ? latestEndOf(prediction) : valueIn(prediction.window, "s")) + (cell.idleBefore === void 0 ? 0 : valueIn(cell.idleBefore, "s"));
|
|
4189
4195
|
const args = [
|
|
4190
4196
|
lab.targetIp,
|
|
4191
4197
|
String(lab.targetPort),
|
|
@@ -4555,7 +4561,7 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
4555
4561
|
"deadline"
|
|
4556
4562
|
], window).catch(() => void 0);
|
|
4557
4563
|
} else if (prediction.detects) {
|
|
4558
|
-
const within =
|
|
4564
|
+
const within = latestEndOf(prediction) * 2e3 + 1e4;
|
|
4559
4565
|
if (cell.observer !== void 0 && cell.observer !== "client") await observerError(cell, partial, within, () => lab.server.slice(serverFrom).some((line) => line.ev === "drop"));
|
|
4560
4566
|
else if (http && cell.probe.mode !== "idle") await probe.until(HTTP_DONE, within).catch(() => void 0);
|
|
4561
4567
|
else await probe.until([
|
|
@@ -4757,6 +4763,6 @@ const runCell = async (cell, say = () => {}) => {
|
|
|
4757
4763
|
}
|
|
4758
4764
|
};
|
|
4759
4765
|
//#endregion
|
|
4760
|
-
export { CONVERGE_MS, INJECT, KEPT, LINGER_MS, LISTING, MEMBER_MARKS, NOISE, PUMP, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, STAGES, STATUS_SCRIPT, STEPS, STEP_DOWN, callOf, clockOffsetOf, closeOrExit, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, dropFinding, dropOf, endpointAfter, errorsOf, exitOf, fieldOf, fieldsOf, firstUnanswered, goHttpImage, groupOnce, groupVerdictOf, harnessImage, hzOf, infoOf, initiateScript, isHopPlace, judge, lagsOf, leftAfter, matches, measure, measureDriver, measureGroup, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, nodeClientImage, nodeHttpImage, noiseOf, onMonotonic, openGroupLab, openLab, openReplicaSetLab, peersOf, pingsOf, proceduresImage, programLines, quantileOf, reconnectFinding, reconnectOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, statusViewOf, subscriptionsOf, targetImageOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
4766
|
+
export { CONVERGE_MS, INJECT, KEPT, LINGER_MS, LISTING, MEMBER_MARKS, NOISE, PUMP, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, STAGES, STATUS_SCRIPT, STEPS, STEP_DOWN, callOf, clockOffsetOf, closeOrExit, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, dropFinding, dropOf, endpointAfter, errorsOf, exitOf, fieldOf, fieldsOf, firstUnanswered, goHttpImage, groupOnce, groupVerdictOf, harnessImage, hzOf, infoOf, initiateScript, isHopPlace, judge, lagsOf, latestEndOf, leftAfter, matches, measure, measureDriver, measureGroup, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, nodeClientImage, nodeHttpImage, noiseOf, onMonotonic, openGroupLab, openLab, openReplicaSetLab, peersOf, pingsOf, proceduresImage, programLines, quantileOf, reconnectFinding, reconnectOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, statusViewOf, subscriptionsOf, targetImageOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
4761
4767
|
|
|
4762
4768
|
//# sourceMappingURL=index.js.map
|