@mark1russell7/harness 0.11.23 → 0.11.25
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 +4 -1
- package/build/{cell-Dn1TvJcA.d.ts → cell-D4HZ1off.d.ts} +29 -3
- package/build/index.d.ts +5 -4
- package/build/index.js +80 -10
- package/build/index.js.map +1 -1
- package/build/schema.d.ts +1 -1
- package/image/native/src/bin/inject.rs +50 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,7 +250,10 @@ ioredis sets the keepalive of its socket itself, after it connects. Thus a cell
|
|
|
250
250
|
- **A close of the server** (`server-close`) makes the server close the connection of each client, with `Target.close`: `CLIENT KILL` for Redis.
|
|
251
251
|
- **A stall of the application** (`app-stall`) injects nothing. The server process holds the reply, because the request asks for it (`HttpProbe.hold`), and its kernel stays up.
|
|
252
252
|
- **An event at a proxy** (`hop-event`, `Cell.event`) is a signal to the program of the proxy, or a query on its console. Examples are a reload of the configuration, a stop of a proxy and PAUSE on the console of PgBouncer.
|
|
253
|
-
- A signal goes to each process of `ProxyHop.program`: HUP, TERM or
|
|
253
|
+
- A signal goes to each process of `ProxyHop.program`: HUP, TERM, INT, STOP or CONT (`HopSignal`). A sidecar in the network and process namespaces of the proxy sends it (`inject signal NAME HUP`). The lab starts that sidecar at the first signal.
|
|
254
|
+
- STOP blocks the event loop of the proxy: its processes stop, and its kernel keeps each socket. With a hold (`Cell.hold`), CONT thaws it at that time after the stop, from inside the VM (`--at`). The lift thaws a proxy that its hold did not thaw.
|
|
255
|
+
- A write (`HopWrite`) changes a file of the proxy, as an edit of its configuration does. The sidecar writes it through the root of the program (`inject write NAME PATH CONTENT`): first a file beside it, then a rename in one step. Thus a program that watches the folder reads the whole file.
|
|
256
|
+
- A write can name a line of the output of the proxy that the change gives (`confirm`), for example the log line of a reload. The event waits 10 s at most for a new such line, else the run fails. Its `undo` writes the first content back after the run.
|
|
254
257
|
- A query goes from the injector of the proxy to its console on 127.0.0.1, as a user that the console trusts (`inject query`). The injector speaks the simple query of the PostgreSQL protocol. Its stamps bracket the query and its ReadyForQuery. After the run, the harness sends the `undo` of the query, for example RESUME after PAUSE.
|
|
255
258
|
- Without `inFlight`, the event comes first, and the request meets it. With `inFlight`, the request goes first, and the event comes while it is in flight. A run whose order is not that order is void.
|
|
256
259
|
- **A fault on a group** acts on the part that `Cell.faultAt` names by its role:
|
|
@@ -130,14 +130,20 @@ interface ProxyHop {
|
|
|
130
130
|
/**
|
|
131
131
|
* A signal of a hop event. HUP reloads the configuration of a pooler. TERM stops a proxy, as a stop of
|
|
132
132
|
* its container does. INT stops docker-proxy, as Docker does when it removes a port binding.
|
|
133
|
+
*
|
|
134
|
+
* STOP blocks the event loop of a proxy: its processes stop, and its kernel keeps each socket. Thus
|
|
135
|
+
* ACKs go on, and the data of each peer waits in its receive queue. With the hold of the cell
|
|
136
|
+
* (`Cell.hold`), CONT thaws it at the end of the hold, from inside the VM.
|
|
133
137
|
*/
|
|
134
|
-
type HopSignal = "HUP" | "TERM" | "INT";
|
|
138
|
+
type HopSignal = "HUP" | "TERM" | "INT" | "STOP" | "CONT";
|
|
135
139
|
/**
|
|
136
140
|
* An event at a proxy hop (`hop-event`). A signal goes to each process of the program of the hop
|
|
137
141
|
* (`ProxyHop.program`). A query goes to the console of the hop from its own namespace, as a user
|
|
138
142
|
* that the console trusts. Its `undo` lifts it after the run. Without `inFlight`, the event
|
|
139
143
|
* comes first, and the request of the client meets it. With `inFlight`, the request goes first,
|
|
140
144
|
* and the event comes while it is in flight.
|
|
145
|
+
*
|
|
146
|
+
* A write changes a file of the hop, as an edit of its configuration does (`HopWrite`).
|
|
141
147
|
*/
|
|
142
148
|
type HopEvent = {
|
|
143
149
|
readonly hop: string;
|
|
@@ -153,7 +159,27 @@ type HopEvent = {
|
|
|
153
159
|
readonly undo?: string;
|
|
154
160
|
};
|
|
155
161
|
readonly inFlight?: true;
|
|
162
|
+
} | {
|
|
163
|
+
readonly hop: string;
|
|
164
|
+
readonly write: HopWrite;
|
|
165
|
+
readonly inFlight?: true;
|
|
156
166
|
};
|
|
167
|
+
/**
|
|
168
|
+
* A hop write puts `content` at `path` in the root of the program of the hop. The content goes to a
|
|
169
|
+
* file beside it first, and a rename puts it in place in one step. Thus a program that watches the
|
|
170
|
+
* folder reads the whole file, not half of it. Its `undo` writes the file of the start again
|
|
171
|
+
* after the run, thus each run makes the same change.
|
|
172
|
+
*
|
|
173
|
+
* `confirm` is a line of the output of the hop that the change gives, for example the log line of
|
|
174
|
+
* a reload. The event waits for a new such line, and a write that the hop does not confirm in 10 s
|
|
175
|
+
* fails the run. Thus a cell that predicts no effect knows that the change took effect.
|
|
176
|
+
*/
|
|
177
|
+
interface HopWrite {
|
|
178
|
+
readonly path: string;
|
|
179
|
+
readonly content: string;
|
|
180
|
+
readonly undo?: string;
|
|
181
|
+
readonly confirm?: string;
|
|
182
|
+
}
|
|
157
183
|
/**
|
|
158
184
|
* A hop is one box between the client and the target. Each hop is on two networks: the network
|
|
159
185
|
* of the side of the client, and the network of the side of the target.
|
|
@@ -1127,5 +1153,5 @@ interface RunTopology {
|
|
|
1127
1153
|
}>>;
|
|
1128
1154
|
}
|
|
1129
1155
|
//#endregion
|
|
1130
|
-
export {
|
|
1131
|
-
//# sourceMappingURL=cell-
|
|
1156
|
+
export { StageBand as $, PinnedImage as A, Replies as B, ImageSource as C, MiddleboxExpectation as D, MiddleboxEvent as E, Reconnects as F, RunGroup as G, RouterHop as H, Recovers as I, SentinelEvent as J, RunPart as K, Reference as L, ProbeEvent as M, ProxyHop as N, MiddleboxForm as O, ProxyTime as P, Stage as Q, ReplicaSet as R, HttpProbe as S, LineMatch as T, RttRange as U, RequestPrediction as V, Run as W, Shipped as X, SentinelStage as Y, ShippedImage as Z, Hop as _, Cluster as a, isHopPlace as at, HopSignal as b, DriverBand as c, FaultPlace as d, Subject as et, Group as f, HarnessImage as g, GroupRole as h, ClientImage as i, WsProbe as it, Prediction as j, Observer as k, DriverStep as l, GroupPrediction as m, Cell as n, Topology as nt, ClusterClientAt as o, latestEndOf as ot, GroupPlace as p, RunTopology as q, Client as r, Toxic as rt, ClusterStage as s, BuildImage as t, Target as tt, Fault as u, HopEvent as v, KernelRecord as w, HopWrite as x, HopPlace as y, ReplicaSetStage as z };
|
|
1157
|
+
//# sourceMappingURL=cell-D4HZ1off.d.ts.map
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as StageBand, A as PinnedImage, B as Replies, C as ImageSource, D as MiddleboxExpectation, E as MiddleboxEvent, F as Reconnects, G as RunGroup, H as RouterHop, I as Recovers, J as SentinelEvent, K as RunPart, L as Reference, M as ProbeEvent, N as ProxyHop, O as MiddleboxForm, P as ProxyTime, Q as Stage, R as ReplicaSet, S as HttpProbe, T as LineMatch, U as RttRange, V as RequestPrediction, W as Run, X as Shipped, Y as SentinelStage, Z as ShippedImage, _ as Hop, a as Cluster, at as isHopPlace, b as HopSignal, c as DriverBand, d as FaultPlace, et as Subject, f as Group, g as HarnessImage, h as GroupRole, i as ClientImage, it as WsProbe, j as Prediction, k as Observer, l as DriverStep, m as GroupPrediction, n as Cell, nt as Topology, o as ClusterClientAt, ot as latestEndOf, p as GroupPlace, q as RunTopology, r as Client, rt as Toxic, s as ClusterStage, t as BuildImage, tt as Target, u as Fault, v as HopEvent, w as KernelRecord, x as HopWrite, y as HopPlace, z as ReplicaSetStage } from "./cell-D4HZ1off.js";
|
|
2
2
|
import { Quantity } from "@mark1russell7/quantity";
|
|
3
3
|
import "testcontainers";
|
|
4
4
|
//#region src/application.d.ts
|
|
@@ -941,9 +941,10 @@ interface Lab {
|
|
|
941
941
|
* This method applies an event at a proxy hop (`HopEvent`), or its undo. A signal goes from a
|
|
942
942
|
* sidecar in the network and process namespaces of the hop. The lab starts that sidecar at the
|
|
943
943
|
* first signal of the hop. A query goes from the injector of the hop to its console on 127.0.0.1. The
|
|
944
|
-
* injector stamps each one. An event with no undo gives null for its undo.
|
|
944
|
+
* injector stamps each one. An event with no undo gives null for its undo. A signal can wait for an
|
|
945
|
+
* instant on CLOCK_MONOTONIC (`at`), as the thaw at the end of a hold does.
|
|
945
946
|
*/
|
|
946
|
-
event(event: HopEvent, undo?: boolean): Promise<{
|
|
947
|
+
event(event: HopEvent, undo?: boolean, at?: number): Promise<{
|
|
947
948
|
t0: number;
|
|
948
949
|
t1: number;
|
|
949
950
|
} | null>;
|
|
@@ -1632,5 +1633,5 @@ export declare const spread: (runs: number, environments: number) => number[];
|
|
|
1632
1633
|
*/
|
|
1633
1634
|
export declare const runCell: (cell: Cell, say?: (line: string) => void) => Promise<CellResult>;
|
|
1634
1635
|
//#endregion
|
|
1635
|
-
export { type Agent, type BuildImage, type CallReading, type Cell, type CellResult, type Client, type ClientImage, type Cluster, type ClusterClientAt, type ClusterLab, type ClusterStage, type Crossings, type Cut, type Decision, type DriverBand, type DriverSocket, type DriverStep, type DriverStepTime, type ErrorOnTheWay, type Fault, type FaultPlace, type FaultSide, type Group, type GroupClient, type GroupLab, type GroupMeasured, type GroupNetworkFault, type GroupPlace, type GroupPrediction, type GroupReading, type GroupRole, type GroupVerdict, type GroupView, type HarnessImage, type Hop, type HopEvent, type HopPlace, type HopSignal, type HttpProbe, type ImagePlan, type ImageSource, type Interval, type KernelRecord, type Lab, type LineMatch, type Manifest, type ManifestHop, type Measured, type MemberLine, type MiddleboxEvent, type MiddleboxExpectation, type MiddleboxForm, type NetworkFault, type NodeLine, type Observer, type Outcome, type PinnedImage, type Prediction, type Probe, type ProbeEvent, type ProgramEvent, type ProxyHop, type ProxyTime, type ReconnectReading, type ReconnectVerdict, type Reconnects, type Recovers, type Reference, type Reordering, type ReplicaSet, type ReplicaSetLab, type ReplicaSetStage, type Replies, type RequestPrediction, type RequestReading, type RouterHop, type RttRange, type Run, type RunGroup, type RunPart, type RunTopology, type SentinelEvent, type SentinelStage, type Shipped, type ShippedImage, type SocketPlace, type Spread, type Stage, type StageBand, type StageVerdict, type Stamp, type Status, type StatusRow, type Subject, type Target, type Topology, type Toxic, type Verdict, type WallStamp, type Writes, type WsProbe, isHopPlace, latestEndOf };
|
|
1636
|
+
export { type Agent, type BuildImage, type CallReading, type Cell, type CellResult, type Client, type ClientImage, type Cluster, type ClusterClientAt, type ClusterLab, type ClusterStage, type Crossings, type Cut, type Decision, type DriverBand, type DriverSocket, type DriverStep, type DriverStepTime, type ErrorOnTheWay, type Fault, type FaultPlace, type FaultSide, type Group, type GroupClient, type GroupLab, type GroupMeasured, type GroupNetworkFault, type GroupPlace, type GroupPrediction, type GroupReading, type GroupRole, type GroupVerdict, type GroupView, type HarnessImage, type Hop, type HopEvent, type HopPlace, type HopSignal, type HopWrite, type HttpProbe, type ImagePlan, type ImageSource, type Interval, type KernelRecord, type Lab, type LineMatch, type Manifest, type ManifestHop, type Measured, type MemberLine, type MiddleboxEvent, type MiddleboxExpectation, type MiddleboxForm, type NetworkFault, type NodeLine, type Observer, type Outcome, type PinnedImage, type Prediction, type Probe, type ProbeEvent, type ProgramEvent, type ProxyHop, type ProxyTime, type ReconnectReading, type ReconnectVerdict, type Reconnects, type Recovers, type Reference, type Reordering, type ReplicaSet, type ReplicaSetLab, type ReplicaSetStage, type Replies, type RequestPrediction, type RequestReading, type RouterHop, type RttRange, type Run, type RunGroup, type RunPart, type RunTopology, type SentinelEvent, type SentinelStage, type Shipped, type ShippedImage, type SocketPlace, type Spread, type Stage, type StageBand, type StageVerdict, type Stamp, type Status, type StatusRow, type Subject, type Target, type Topology, type Toxic, type Verdict, type WallStamp, type Writes, type WsProbe, isHopPlace, latestEndOf };
|
|
1636
1637
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.js
CHANGED
|
@@ -2092,6 +2092,11 @@ const PUMP_SLACK = {
|
|
|
2092
2092
|
};
|
|
2093
2093
|
/** This count is how many of the last lines of a proxy hop the lab keeps. The error of a hop that does not start gives them. */
|
|
2094
2094
|
const TAIL_LINES = 60;
|
|
2095
|
+
/** This count is how many lines of the output of a proxy hop the lab keeps, for a write that the proxy confirms. */
|
|
2096
|
+
const OUTPUT_LINES = 2e3;
|
|
2097
|
+
/** This time is how long a write of a hop waits for the line that confirms it, in milliseconds. */
|
|
2098
|
+
const CONFIRM_MS = 1e4;
|
|
2099
|
+
const sleep$4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2095
2100
|
/** This function gives the arguments of the injector for the options of a fault. */
|
|
2096
2101
|
const optionArgs = (options) => [
|
|
2097
2102
|
...options.direction === void 0 ? [] : ["--direction", options.direction],
|
|
@@ -2328,6 +2333,7 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2328
2333
|
opened.push(server);
|
|
2329
2334
|
const serverIp = await ipOn(server, last);
|
|
2330
2335
|
const middlebox = [];
|
|
2336
|
+
const hopOutput = /* @__PURE__ */ new Map();
|
|
2331
2337
|
const inChain = [];
|
|
2332
2338
|
const endpointOf = (element) => {
|
|
2333
2339
|
const at = endpointAfter(hops, element);
|
|
@@ -2386,6 +2392,11 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2386
2392
|
})));
|
|
2387
2393
|
if (hop.ready !== void 0) proxy = proxy.withWaitStrategy(Wait.forLogMessage(hop.ready));
|
|
2388
2394
|
const tail = [];
|
|
2395
|
+
const output = {
|
|
2396
|
+
lines: [],
|
|
2397
|
+
total: 0
|
|
2398
|
+
};
|
|
2399
|
+
hopOutput.set(hop.name, output);
|
|
2389
2400
|
proxy = proxy.withLogConsumer((stream) => {
|
|
2390
2401
|
let rest = "";
|
|
2391
2402
|
stream.on("data", (chunk) => {
|
|
@@ -2394,6 +2405,9 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2394
2405
|
for (const line of lines) {
|
|
2395
2406
|
tail.push(line);
|
|
2396
2407
|
if (tail.length > TAIL_LINES) tail.shift();
|
|
2408
|
+
output.lines.push(line);
|
|
2409
|
+
output.total += 1;
|
|
2410
|
+
if (output.lines.length > OUTPUT_LINES) output.lines.shift();
|
|
2397
2411
|
}
|
|
2398
2412
|
});
|
|
2399
2413
|
});
|
|
@@ -2803,25 +2817,58 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2803
2817
|
...when
|
|
2804
2818
|
]);
|
|
2805
2819
|
},
|
|
2806
|
-
async event(event, undo = false) {
|
|
2820
|
+
async event(event, undo = false, at) {
|
|
2807
2821
|
const hop = hopNamed(event.hop);
|
|
2808
2822
|
if (hop.hop.kind !== "proxy") throw new Error(`the hop ${event.hop} is a router: an event goes to a proxy`);
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
const program = hop.hop.program;
|
|
2812
|
-
if (program === void 0) throw new Error(`the hop ${event.hop} names no program to signal`);
|
|
2823
|
+
const program = hop.hop.program;
|
|
2824
|
+
const sidecarOf = async () => {
|
|
2813
2825
|
let sidecar = hopSignallers.get(event.hop);
|
|
2814
2826
|
if (sidecar === void 0) {
|
|
2815
|
-
sidecar = await withPid(new GenericContainer(image).withNetworkMode(`container:${hop.box.getId()}`).withAddedCapabilities("NET_ADMIN", "NET_RAW"), `container:${hop.box.getId()}`).start();
|
|
2827
|
+
sidecar = await withPid(new GenericContainer(image).withNetworkMode(`container:${hop.box.getId()}`).withAddedCapabilities("NET_ADMIN", "NET_RAW", "SYS_PTRACE"), `container:${hop.box.getId()}`).start();
|
|
2816
2828
|
hopSignallers.set(event.hop, sidecar);
|
|
2817
2829
|
opened.push(sidecar);
|
|
2818
2830
|
}
|
|
2819
|
-
return
|
|
2831
|
+
return sidecar;
|
|
2832
|
+
};
|
|
2833
|
+
if ("signal" in event) {
|
|
2834
|
+
if (undo) return null;
|
|
2835
|
+
if (program === void 0) throw new Error(`the hop ${event.hop} names no program to signal`);
|
|
2836
|
+
const when = at === void 0 ? [] : ["--at", String(Math.round(at))];
|
|
2837
|
+
return stamped(await sidecarOf(), [
|
|
2820
2838
|
"/opt/harness/inject",
|
|
2821
2839
|
"signal",
|
|
2822
2840
|
program,
|
|
2823
|
-
event.signal
|
|
2841
|
+
event.signal,
|
|
2842
|
+
...when
|
|
2843
|
+
]);
|
|
2844
|
+
}
|
|
2845
|
+
if ("write" in event) {
|
|
2846
|
+
const content = undo ? event.write.undo : event.write.content;
|
|
2847
|
+
if (content === void 0) return null;
|
|
2848
|
+
if (program === void 0) throw new Error(`the hop ${event.hop} names no program whose root a write reaches`);
|
|
2849
|
+
const output = hopOutput.get(event.hop);
|
|
2850
|
+
const before = output?.total ?? 0;
|
|
2851
|
+
const stamp = await stamped(await sidecarOf(), [
|
|
2852
|
+
"/opt/harness/inject",
|
|
2853
|
+
"write",
|
|
2854
|
+
program,
|
|
2855
|
+
event.write.path,
|
|
2856
|
+
content
|
|
2824
2857
|
]);
|
|
2858
|
+
const confirm = event.write.confirm;
|
|
2859
|
+
if (confirm !== void 0) {
|
|
2860
|
+
const started = Date.now();
|
|
2861
|
+
const confirmed = () => {
|
|
2862
|
+
if (output === void 0) return false;
|
|
2863
|
+
const fresh = Math.min(output.total - before, output.lines.length);
|
|
2864
|
+
return output.lines.slice(output.lines.length - fresh).some((line) => line.includes(confirm));
|
|
2865
|
+
};
|
|
2866
|
+
while (!confirmed()) {
|
|
2867
|
+
if (Date.now() - started > CONFIRM_MS) throw new Error(`the hop ${event.hop} did not confirm the write of ${event.write.path}: no line "${confirm}" in ${CONFIRM_MS / 1e3} s`);
|
|
2868
|
+
await sleep$4(50);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
return stamp;
|
|
2825
2872
|
}
|
|
2826
2873
|
const query = undo ? event.console.undo : event.console.query;
|
|
2827
2874
|
if (query === void 0) return null;
|
|
@@ -5324,9 +5371,23 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
5324
5371
|
case "server-close":
|
|
5325
5372
|
await lab.serverClose();
|
|
5326
5373
|
return null;
|
|
5327
|
-
case "hop-event":
|
|
5374
|
+
case "hop-event": {
|
|
5328
5375
|
if (cell.event === void 0) throw new Error(`${cell.name} names no event`);
|
|
5329
|
-
|
|
5376
|
+
const stamp = await lab.event(cell.event);
|
|
5377
|
+
if ("signal" in cell.event && cell.event.signal === "STOP" && cell.hold !== void 0 && stamp !== null) {
|
|
5378
|
+
const thaw = {
|
|
5379
|
+
hop: cell.event.hop,
|
|
5380
|
+
signal: "CONT"
|
|
5381
|
+
};
|
|
5382
|
+
thawing = lab.event(thaw, false, stamp.t0 + valueIn(cell.hold, "s") * 1e9).then((thawed) => {
|
|
5383
|
+
if (thawed === null) throw new Error(`the hop ${thaw.hop} gave no stamp for CONT`);
|
|
5384
|
+
heal = thawed;
|
|
5385
|
+
return thawed;
|
|
5386
|
+
});
|
|
5387
|
+
thawing.catch(() => void 0);
|
|
5388
|
+
}
|
|
5389
|
+
return stamp;
|
|
5390
|
+
}
|
|
5330
5391
|
default: return lab.fault("blackhole", faultAt);
|
|
5331
5392
|
}
|
|
5332
5393
|
};
|
|
@@ -5346,6 +5407,15 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
5346
5407
|
case "app-stall":
|
|
5347
5408
|
case "none": return;
|
|
5348
5409
|
case "hop-event":
|
|
5410
|
+
if (cell.event !== void 0 && "signal" in cell.event && cell.event.signal === "STOP") {
|
|
5411
|
+
const thaw = {
|
|
5412
|
+
hop: cell.event.hop,
|
|
5413
|
+
signal: "CONT"
|
|
5414
|
+
};
|
|
5415
|
+
if (thawing !== null) await thawing.catch(() => lab.event(thaw));
|
|
5416
|
+
else await lab.event(thaw);
|
|
5417
|
+
return;
|
|
5418
|
+
}
|
|
5349
5419
|
if (cell.event !== void 0) await lab.event(cell.event, true);
|
|
5350
5420
|
return;
|
|
5351
5421
|
case "unreachable": return lab.lift("reject", faultAt, { code: cell.icmp ?? "host" });
|