@mark1russell7/harness 0.11.24 → 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 +2 -0
- package/build/{cell-wInJC-GH.d.ts → cell-D4HZ1off.d.ts} +24 -2
- package/build/index.d.ts +2 -2
- package/build/index.js +51 -6
- 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
|
@@ -252,6 +252,8 @@ ioredis sets the keepalive of its socket itself, after it connects. Thus a cell
|
|
|
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
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
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.
|
|
255
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.
|
|
256
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.
|
|
257
259
|
- **A fault on a group** acts on the part that `Cell.faultAt` names by its role:
|
|
@@ -142,6 +142,8 @@ type HopSignal = "HUP" | "TERM" | "INT" | "STOP" | "CONT";
|
|
|
142
142
|
* that the console trusts. Its `undo` lifts it after the run. Without `inFlight`, the event
|
|
143
143
|
* comes first, and the request of the client meets it. With `inFlight`, the request goes first,
|
|
144
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`).
|
|
145
147
|
*/
|
|
146
148
|
type HopEvent = {
|
|
147
149
|
readonly hop: string;
|
|
@@ -157,7 +159,27 @@ type HopEvent = {
|
|
|
157
159
|
readonly undo?: string;
|
|
158
160
|
};
|
|
159
161
|
readonly inFlight?: true;
|
|
162
|
+
} | {
|
|
163
|
+
readonly hop: string;
|
|
164
|
+
readonly write: HopWrite;
|
|
165
|
+
readonly inFlight?: true;
|
|
160
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
|
+
}
|
|
161
183
|
/**
|
|
162
184
|
* A hop is one box between the client and the target. Each hop is on two networks: the network
|
|
163
185
|
* of the side of the client, and the network of the side of the target.
|
|
@@ -1131,5 +1153,5 @@ interface RunTopology {
|
|
|
1131
1153
|
}>>;
|
|
1132
1154
|
}
|
|
1133
1155
|
//#endregion
|
|
1134
|
-
export {
|
|
1135
|
-
//# 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
|
|
@@ -1633,5 +1633,5 @@ export declare const spread: (runs: number, environments: number) => number[];
|
|
|
1633
1633
|
*/
|
|
1634
1634
|
export declare const runCell: (cell: Cell, say?: (line: string) => void) => Promise<CellResult>;
|
|
1635
1635
|
//#endregion
|
|
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 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 };
|
|
1637
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
|
});
|
|
@@ -2806,18 +2820,21 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2806
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
|
}
|
|
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`);
|
|
2819
2836
|
const when = at === void 0 ? [] : ["--at", String(Math.round(at))];
|
|
2820
|
-
return stamped(
|
|
2837
|
+
return stamped(await sidecarOf(), [
|
|
2821
2838
|
"/opt/harness/inject",
|
|
2822
2839
|
"signal",
|
|
2823
2840
|
program,
|
|
@@ -2825,6 +2842,34 @@ const openLab = async (target, kind = "tcp", topology) => {
|
|
|
2825
2842
|
...when
|
|
2826
2843
|
]);
|
|
2827
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
|
|
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;
|
|
2872
|
+
}
|
|
2828
2873
|
const query = undo ? event.console.undo : event.console.query;
|
|
2829
2874
|
if (query === void 0) return null;
|
|
2830
2875
|
const { port, user, database } = event.console;
|